Merge pull request #6 from schizza/Ch2Support

Add support for channel 2
pull/8/head v0.1.2
schizza 2024-03-17 20:36:23 +01:00 committed by GitHub
commit accc0c1ca8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 141 additions and 42 deletions

View File

@ -12,7 +12,7 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import API_ID, API_KEY, DEFAULT_URL, DEV_DBG, DOMAIN, WINDY_ENABLED from .const import API_ID, API_KEY, DEFAULT_URL, DEV_DBG, DOMAIN, WINDY_ENABLED
from .utils import anonymize, remap_items from .utils import anonymize, check_disabled, remap_items
from .windy_func import WindyPush from .windy_func import WindyPush
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -55,7 +55,11 @@ class WeatherDataUpdateCoordinator(DataUpdateCoordinator):
if self.config_entry.options.get(WINDY_ENABLED): if self.config_entry.options.get(WINDY_ENABLED):
response = await self.windy.push_data_to_windy(data) response = await self.windy.push_data_to_windy(data)
self.async_set_updated_data(remap_items(data)) remaped_items = remap_items(data)
await check_disabled(self.hass, remaped_items, self.config_entry.options.get(DEV_DBG))
self.async_set_updated_data(remaped_items)
if self.config_entry.options.get(DEV_DBG): if self.config_entry.options.get(DEV_DBG):
_LOGGER.info("Dev log: %s", anonymize(data)) _LOGGER.info("Dev log: %s", anonymize(data))
@ -72,8 +76,12 @@ def register_path(
route = hass.http.app.router.add_route( route = hass.http.app.router.add_route(
"GET", url_path, coordinator.recieved_data "GET", url_path, coordinator.recieved_data
) )
except Exception: # pylint: disable=(broad-except) except RuntimeError as Ex: # pylint: disable=(broad-except)
_LOGGER.error("Unable to register URL handler!") if "Added route will never be executed, method GET is already registered" in Ex.args:
_LOGGER.info("Handler to URL (%s) already registred", url_path)
return True
_LOGGER.error("Unable to register URL handler! (%s)", Ex.args)
return False return False
_LOGGER.info( _LOGGER.info(
@ -82,8 +90,7 @@ def register_path(
) )
return True return True
def unregister_path(hass: HomeAssistant):
def unregister_path():
"""Unregister path to handle incoming data.""" """Unregister path to handle incoming data."""
_LOGGER.error( _LOGGER.error(
"Unable to delete webhook from API! Restart HA before adding integration!" "Unable to delete webhook from API! Restart HA before adding integration!"
@ -99,8 +106,10 @@ class Weather(WeatherDataUpdateCoordinator):
super().__init__(hass, config) super().__init__(hass, config)
async def setup_update_listener(self, hass: HomeAssistant, entry: ConfigEntry): async def setup_update_listener(self, hass: HomeAssistant, entry: ConfigEntry):
"""Update setup listener.""" """Update setup listener."""
_LOGGER.info("Settings updated") await hass.config_entries.async_reload(entry.entry_id)
_LOGGER.info("Settings updated")
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
@ -131,7 +140,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) _ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if _ok: if _ok:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
unregister_path() unregister_path(hass)
return _ok return _ok

View File

@ -84,3 +84,8 @@ REMAP_ITEMS: dict = {
"soiltempf": CH2_TEMP, "soiltempf": CH2_TEMP,
"soilmoisture": CH2_HUMIDITY, "soilmoisture": CH2_HUMIDITY,
} }
DISABLED_BY_DEFAULT: Final = [
CH2_TEMP,
CH2_HUMIDITY
]

View File

@ -10,6 +10,6 @@
"iot_class": "local_push", "iot_class": "local_push",
"requirements": [], "requirements": [],
"ssdp": [], "ssdp": [],
"version": "0.1.1", "version": "0.1.2",
"zeroconf": [] "zeroconf": []
} }

View File

@ -1,9 +1,11 @@
"""Sensors definition for SWS12500.""" """Sensors definition for SWS12500."""
from dataclasses import dataclass
from collections.abc import Callable from collections.abc import Callable
from typing import Any from dataclasses import dataclass
import logging
from typing import Any, cast
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
RestoreSensor,
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
@ -11,21 +13,20 @@ from homeassistant.components.sensor import (
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import (
DEGREE,
PERCENTAGE,
UV_INDEX,
UnitOfIrradiance, UnitOfIrradiance,
UnitOfPrecipitationDepth, UnitOfPrecipitationDepth,
UnitOfVolumetricFlux,
UnitOfPressure, UnitOfPressure,
UnitOfSpeed, UnitOfSpeed,
UnitOfTemperature, UnitOfTemperature,
DEGREE, UnitOfVolumetricFlux,
UV_INDEX,
PERCENTAGE
) )
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceEntryType from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import WeatherDataUpdateCoordinator from . import WeatherDataUpdateCoordinator
@ -48,13 +49,15 @@ from .const import (
WIND_SPEED, WIND_SPEED,
) )
_LOGGER = logging.getLogger(__name__)
@dataclass
@dataclass(frozen=True, kw_only=True)
class WeatherSensorEntityDescription(SensorEntityDescription): class WeatherSensorEntityDescription(SensorEntityDescription):
"""Describe Weather Sensor entities.""" """Describe Weather Sensor entities."""
attr_fn: Callable[[dict[str, Any]], dict[str, StateType]] = lambda _: {} value_fn: Callable[[Any], int | float | str | None]
unit_fn: Callable[[bool], str | None] = lambda _: None
SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = ( SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
@ -64,6 +67,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
icon="mdi:thermometer", icon="mdi:thermometer",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
translation_key=INDOOR_TEMP, translation_key=INDOOR_TEMP,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=INDOOR_HUMIDITY, key=INDOOR_HUMIDITY,
@ -72,6 +76,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
icon="mdi:thermometer", icon="mdi:thermometer",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
translation_key=INDOOR_HUMIDITY, translation_key=INDOOR_HUMIDITY,
value_fn=lambda data: cast(int, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=OUTSIDE_TEMP, key=OUTSIDE_TEMP,
@ -80,6 +85,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
icon="mdi:thermometer", icon="mdi:thermometer",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
translation_key=OUTSIDE_TEMP, translation_key=OUTSIDE_TEMP,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=OUTSIDE_HUMIDITY, key=OUTSIDE_HUMIDITY,
@ -88,6 +94,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
icon="mdi:thermometer", icon="mdi:thermometer",
device_class=SensorDeviceClass.HUMIDITY, device_class=SensorDeviceClass.HUMIDITY,
translation_key=OUTSIDE_HUMIDITY, translation_key=OUTSIDE_HUMIDITY,
value_fn=lambda data: cast(int, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=DEW_POINT, key=DEW_POINT,
@ -96,6 +103,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
icon="mdi:thermometer-lines", icon="mdi:thermometer-lines",
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
translation_key=DEW_POINT, translation_key=DEW_POINT,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=BARO_PRESSURE, key=BARO_PRESSURE,
@ -105,6 +113,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE, device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
suggested_unit_of_measurement=UnitOfPressure.HPA, suggested_unit_of_measurement=UnitOfPressure.HPA,
translation_key=BARO_PRESSURE, translation_key=BARO_PRESSURE,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=WIND_SPEED, key=WIND_SPEED,
@ -114,6 +123,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
icon="mdi:weather-windy", icon="mdi:weather-windy",
translation_key=WIND_SPEED, translation_key=WIND_SPEED,
value_fn=lambda data: cast(int, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=WIND_GUST, key=WIND_GUST,
@ -123,6 +133,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR, suggested_unit_of_measurement=UnitOfSpeed.KILOMETERS_PER_HOUR,
icon="mdi:windsock", icon="mdi:windsock",
translation_key=WIND_GUST, translation_key=WIND_GUST,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=WIND_DIR, key=WIND_DIR,
@ -131,6 +142,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
suggested_display_precision=None, suggested_display_precision=None,
icon="mdi:sign-direction", icon="mdi:sign-direction",
translation_key=WIND_DIR, translation_key=WIND_DIR,
value_fn=lambda data: cast(int, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=RAIN, key=RAIN,
@ -141,6 +153,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
suggested_display_precision=2, suggested_display_precision=2,
icon="mdi:weather-pouring", icon="mdi:weather-pouring",
translation_key=RAIN, translation_key=RAIN,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=DAILY_RAIN, key=DAILY_RAIN,
@ -151,6 +164,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
suggested_display_precision=2, suggested_display_precision=2,
icon="mdi:weather-pouring", icon="mdi:weather-pouring",
translation_key=DAILY_RAIN, translation_key=DAILY_RAIN,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=SOLAR_RADIATION, key=SOLAR_RADIATION,
@ -159,6 +173,7 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.IRRADIANCE, device_class=SensorDeviceClass.IRRADIANCE,
icon="mdi:weather-sunny", icon="mdi:weather-sunny",
translation_key=SOLAR_RADIATION, translation_key=SOLAR_RADIATION,
value_fn=lambda data: cast(float, data),
), ),
WeatherSensorEntityDescription( WeatherSensorEntityDescription(
key=UV, key=UV,
@ -166,6 +181,28 @@ SENSOR_TYPES: tuple[WeatherSensorEntityDescription, ...] = (
native_unit_of_measurement=UV_INDEX, native_unit_of_measurement=UV_INDEX,
icon="mdi:sunglasses", icon="mdi:sunglasses",
translation_key=UV, translation_key=UV,
value_fn=lambda data: cast(float, data),
),
WeatherSensorEntityDescription(
key=CH2_TEMP,
native_unit_of_measurement=UnitOfTemperature.FAHRENHEIT,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
suggested_unit_of_measurement=UnitOfTemperature.CELSIUS,
icon="mdi:weather-sunny",
translation_key=CH2_TEMP,
entity_registry_visible_default=False,
value_fn=lambda data: cast(float, data),
),
WeatherSensorEntityDescription(
key=CH2_HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.HUMIDITY,
icon="mdi:weather-sunny",
translation_key=CH2_HUMIDITY,
entity_registry_visible_default=False,
value_fn=lambda data: cast(int, data),
), ),
) )
@ -174,7 +211,6 @@ async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
discovery_info=None,
) -> None: ) -> None:
"""Set up Weather Station sensors.""" """Set up Weather Station sensors."""
@ -185,7 +221,9 @@ async def async_setup_entry(
async_add_entities(sensors) async_add_entities(sensors)
class WeatherSensor(CoordinatorEntity[WeatherDataUpdateCoordinator], SensorEntity): class WeatherSensor(
CoordinatorEntity[WeatherDataUpdateCoordinator], RestoreSensor, SensorEntity
):
"""Implementation of Weather Sensor entity.""" """Implementation of Weather Sensor entity."""
entity_description: WeatherSensorEntityDescription entity_description: WeatherSensorEntityDescription
@ -203,32 +241,35 @@ class WeatherSensor(CoordinatorEntity[WeatherDataUpdateCoordinator], SensorEntit
self.hass = hass self.hass = hass
self.coordinator = coordinator self.coordinator = coordinator
self.entity_description = description self.entity_description = description
self._state: StateType = None self._attr_unique_id = description.key
self._available = False self._data = None
self._attr_unique_id = f"{DOMAIN}.{description.key}"
async def async_added_to_hass(self) -> None:
"""Handle disabled entities that has previous data."""
await super().async_added_to_hass()
self.coordinator.async_add_listener(self._handle_coordinator_update)
prev_state_data = await self.async_get_last_sensor_data()
prev_state = await self.async_get_last_state()
if not prev_state:
return
self._data = prev_state_data.native_value
if not self.entity_registry_visible_default:
self.entity_registry_visible_default = True
@callback @callback
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator.""" """Handle updated data from the coordinator."""
self._state = self.coordinator.data.get(self.entity_description.key) self._data = self.coordinator.data.get(self.entity_description.key)
self.async_write_ha_state() self.async_write_ha_state()
self.async_registry_entry_updated()
@property @property
def unique_id(self) -> str: def native_value(self) -> str | int | float | None:
"""Return a unique, Home Assistant friendly identifier for this entity."""
return self.entity_description.key
@property
def native_value(self) -> None:
"""Return value of entity.""" """Return value of entity."""
return self._state return self.entity_description.value_fn(self._data)
@property
def native_unit_of_measurement(self) -> str:
"""Return unit of measurement."""
return str(self.entity_description.native_unit_of_measurement)
@property @property
def state_class(self) -> str: def state_class(self) -> str:

View File

@ -1,9 +1,16 @@
"""Utils for SWS12500.""" """Utils for SWS12500."""
from homeassistant.config_entries import ConfigEntry import logging
from homeassistant.core import HomeAssistant
from .const import REMAP_ITEMS from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry
from homeassistant.SensorEntity import async_get as se
from .const import DISABLED_BY_DEFAULT, DOMAIN, REMAP_ITEMS
_LOGGER = logging.getLogger(__name__)
def update_options( def update_options(
@ -39,3 +46,40 @@ def remap_items(entities):
items[REMAP_ITEMS[item]] = entities[item] items[REMAP_ITEMS[item]] = entities[item]
return items return items
async def check_disabled(hass: HomeAssistant, items, log: bool = False):
"""Check if we have data for disabed sensors.
If so, then enable senosor.
Returns True if sensor found else False
"""
_ER = entity_registry.async_get(hass)
_SE = se(hass)
eid: str = None
entityFound: bool = False
for disabled in DISABLED_BY_DEFAULT:
if log:
_LOGGER.info("Checking %s", disabled)
if disabled in items:
eid = _ER.async_get_entity_id(Platform.SENSOR, DOMAIN, disabled)
is_disabled = _ER.entities[eid].hidden
if log:
_LOGGER.info("Found sensor %s", eid)
if is_disabled:
if log:
_LOGGER.info("Sensor %s is hidden. Making visible", eid)
_ER.async_update_entity(eid, hidden_by=None)
entityFound = True
elif not is_disabled and log:
_LOGGER.info("Sensor %s is visible.", eid)
return entityFound