fix: address review findings across forwarders and helpers
- wind_dir_to_text: treat 0/missing direction as None (calm) so a missing wind direction no longer renders as North; azimut lambdas pass None too. - windy: log response status (was logging an un-awaited coroutine method); count "unexpected response" toward the disable threshold even when logging is off. - pocasi: disable threshold >3 -> >=3 to match Windy's 3-strike behavior. - ecowitt: always attach device_info to native sensors, including unknown sensor types (previously left orphaned without a device). - const: stop remapping WSLink connection flags (t1cn/t234cXcn) into the payload; they had no entity and leaked ghost *_connection keys into data and persisted SENSORS_TO_LOAD. Gating uses the raw keys and is unaffected. - config_flow: fix WSLink port fallback typo 433 -> 443; drop mutable default argument on async_step_init. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>ecowitt_support
parent
6167a3a536
commit
2b4e1a5f8c
|
|
@ -141,7 +141,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
|||
|
||||
self.wslink_addon_port = {WSLINK_ADDON_PORT: self.config_entry.options.get(WSLINK_ADDON_PORT, 443)}
|
||||
|
||||
async def async_step_init(self, user_input: dict[str, Any] = {}):
|
||||
async def async_step_init(self, user_input: dict[str, Any] | None = None):
|
||||
"""Manage the options - show menu first."""
|
||||
_ = user_input
|
||||
return self.async_show_menu(
|
||||
|
|
@ -295,7 +295,7 @@ class ConfigOptionsFlowHandler(OptionsFlow):
|
|||
await self._get_entry_data()
|
||||
|
||||
if not (port := self.wslink_addon_port.get(WSLINK_ADDON_PORT)):
|
||||
port = 433
|
||||
port = 443
|
||||
|
||||
wslink_port_schema = {
|
||||
vol.Required(WSLINK_ADDON_PORT, default=port): int,
|
||||
|
|
|
|||
|
|
@ -262,14 +262,10 @@ REMAP_WSLINK_ITEMS: dict[str, str] = {
|
|||
"t1uvi": UV,
|
||||
"t234c1tem": CH2_TEMP,
|
||||
"t234c1hum": CH2_HUMIDITY,
|
||||
"t1cn": OUTSIDE_CONNECTION,
|
||||
"t234c1cn": CH2_CONNECTION,
|
||||
"t234c2cn": CH3_CONNECTION,
|
||||
"t234c3cn": CH4_CONNECTION,
|
||||
"t234c4cn": CH5_CONNECTION,
|
||||
"t234c5cn": CH6_CONNECTION,
|
||||
"t234c6cn": CH7_CONNECTION,
|
||||
"t234c7cn": CH8_CONNECTION,
|
||||
# NOTE: connection flags (t1cn / t234cXcn / t9cn) are intentionally NOT remapped.
|
||||
# They are used only as gating inputs (see CONNECTION_GATED_SENSORS), which read the
|
||||
# raw payload keys. Remapping them used to leak ghost "*_connection" keys (with no
|
||||
# entity) into the coordinator data and into persisted SENSORS_TO_LOAD.
|
||||
"t1chill": CHILL_INDEX,
|
||||
"t1heat": HEAT_INDEX,
|
||||
"t1rainhr": HOURLY_RAIN,
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ class EcoWittNativeSensor(SensorEntity):
|
|||
self._attr_translation_key = None # we do not have translation_keys for native sensors
|
||||
self._attr_name = sensor.name # default name, can be overridden by translation_key if we had one
|
||||
|
||||
# set HomeAssistant metadata from aioecowitt sensor type
|
||||
# set HomeAssistant metadata from aioecowitt sensor type.
|
||||
# Unknown types still get a usable entity (raw value, no device class/unit).
|
||||
ha_meta = STYPE_TO_HA.get(sensor.stype)
|
||||
if ha_meta:
|
||||
device_class, unit, state_class = ha_meta
|
||||
|
|
@ -259,15 +260,17 @@ class EcoWittNativeSensor(SensorEntity):
|
|||
self._attr_native_unit_of_measurement = unit
|
||||
self._attr_state_class = state_class
|
||||
|
||||
station = sensor.station
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections=set(),
|
||||
name=f"Ecowitt {station.model}" if station else "Ecowitt station",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, f"ecowitt_{station.key}" if station else "ecowitt")},
|
||||
manufacturer="Ecowitt impl. from Schizza for SWS12500",
|
||||
model=station.model if station else None,
|
||||
)
|
||||
# Always attach device info so the entity is grouped under the Ecowitt
|
||||
# station device even when its sensor type has no HA mapping.
|
||||
station = sensor.station
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections=set(),
|
||||
name=f"Ecowitt {station.model}" if station else "Ecowitt station",
|
||||
entry_type=DeviceEntryType.SERVICE,
|
||||
identifiers={(DOMAIN, f"ecowitt_{station.key}" if station else "ecowitt")},
|
||||
manufacturer="Ecowitt impl. from Schizza for SWS12500",
|
||||
model=station.model if station else None,
|
||||
)
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType | datetime: # pyright: ignore[reportIncompatibleVariableOverride]
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@ class PocasiPush:
|
|||
self.last_error = str(ex)
|
||||
_LOGGER.critical("Invalid response from Pocasi Meteo: %s", str(ex))
|
||||
self.invalid_response_count += 1
|
||||
if self.invalid_response_count > 3:
|
||||
if self.invalid_response_count >= 3:
|
||||
_LOGGER.critical(POCASI_CZ_UNEXPECTED)
|
||||
self.enabled = False
|
||||
await update_options(self.hass, self.config, POCASI_CZ_ENABLED, False)
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ SENSOR_TYPES_WEATHER_API: tuple[WeatherSensorEntityDescription, ...] = (
|
|||
WeatherSensorEntityDescription(
|
||||
key=WIND_AZIMUT,
|
||||
icon="mdi:sign-direction",
|
||||
value_from_data_fn=lambda dir: wind_dir_to_text(dir.get(WIND_DIR, 0.0)),
|
||||
value_from_data_fn=lambda dir: wind_dir_to_text(dir.get(WIND_DIR)),
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[e.value for e in UnitOfDir],
|
||||
translation_key=WIND_AZIMUT,
|
||||
|
|
|
|||
|
|
@ -160,7 +160,7 @@ SENSOR_TYPES_WSLINK: tuple[WeatherSensorEntityDescription, ...] = (
|
|||
WeatherSensorEntityDescription(
|
||||
key=WIND_AZIMUT,
|
||||
icon="mdi:sign-direction",
|
||||
value_from_data_fn=lambda dir: wind_dir_to_text(dir.get(WIND_DIR, 0.0)),
|
||||
value_from_data_fn=lambda dir: wind_dir_to_text(dir.get(WIND_DIR)),
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
options=[e.value for e in UnitOfDir],
|
||||
translation_key=WIND_AZIMUT,
|
||||
|
|
|
|||
|
|
@ -191,15 +191,19 @@ def check_disabled(items: dict[str, str], config_entry: ConfigEntry) -> list[str
|
|||
def wind_dir_to_text(deg: float) -> UnitOfDir | None:
|
||||
"""Return wind direction in text representation.
|
||||
|
||||
A direction of 0 - or a missing/invalid value - is treated as "no reading"
|
||||
(calm) and returns None, so a missing wind direction does not render as North.
|
||||
|
||||
Returns UnitOfDir or None
|
||||
"""
|
||||
|
||||
_deg = to_float(deg)
|
||||
if _deg is not None:
|
||||
_LOGGER.debug("wind_dir: %s", AZIMUT[int(abs((_deg - 11.25) % 360) / 22.5)])
|
||||
return AZIMUT[int(abs((_deg - 11.25) % 360) / 22.5)]
|
||||
if _deg is None or _deg == 0:
|
||||
return None
|
||||
|
||||
return None
|
||||
azimut = AZIMUT[int(abs((_deg - 11.25) % 360) / 22.5)]
|
||||
_LOGGER.debug("wind_dir: %s", azimut)
|
||||
return azimut
|
||||
|
||||
|
||||
def battery_level(battery: int | str | None) -> UnitOfBat:
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ class WindyPush:
|
|||
"""Verify answer form Windy."""
|
||||
|
||||
if self.log and response:
|
||||
_LOGGER.info("Windy raw response: %s", response.text)
|
||||
# response.text is a coroutine; we are in a sync method here, so log the
|
||||
# status instead of awaiting/logging a bound method object.
|
||||
_LOGGER.info("Windy raw response status: %s", response.status)
|
||||
|
||||
if response.status == 200:
|
||||
raise WindySuccess
|
||||
|
|
@ -270,8 +272,10 @@ class WindyPush:
|
|||
else:
|
||||
self.last_status = "unexpected_response"
|
||||
self.last_error = "Unexpected response from Windy."
|
||||
# Always count unexpected responses toward the disable threshold,
|
||||
# regardless of the logging setting.
|
||||
self.invalid_response_count += 1
|
||||
if self.log:
|
||||
self.invalid_response_count += 1
|
||||
_LOGGER.debug(
|
||||
"Unexpected response from Windy. Max retries before disabling resend function: %s",
|
||||
(WINDY_MAX_RETRIES - self.invalid_response_count),
|
||||
|
|
|
|||
Loading…
Reference in New Issue