diff --git a/custom_components/davis_vantage/__init__.py b/custom_components/davis_vantage/__init__.py index 7e6ca3f..ba5d194 100755 --- a/custom_components/davis_vantage/__init__.py +++ b/custom_components/davis_vantage/__init__.py @@ -1,4 +1,5 @@ """The Davis Vantage integration.""" + from __future__ import annotations from typing import Any import logging @@ -24,21 +25,20 @@ CONFIG_INTERVAL, CONFIG_PROTOCOL, CONFIG_LINK, - DATA_ARCHIVE_PERIOD + DATA_ARCHIVE_PERIOD, ) from .coordinator import DavisVantageDataUpdateCoordinator from .utils import convert_to_iso_datetime -PLATFORMS: list[Platform] = [ - Platform.SENSOR, - Platform.BINARY_SENSOR -] +PLATFORMS: list[Platform] = [Platform.SENSOR, Platform.BINARY_SENSOR] _LOGGER: logging.Logger = logging.getLogger(__package__) + async def async_setup(hass: HomeAssistant, config: Any) -> bool: return True + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Davis Vantage from a config entry.""" if hass.data.get(DOMAIN) is None: @@ -50,13 +50,18 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: link = entry.data.get(CONFIG_LINK, "") rain_collector = entry.data.get(CONFIG_RAIN_COLLECTOR, RAIN_COLLECTOR_IMPERIAL) - hass.data[DOMAIN]['interval'] = entry.data.get(CONFIG_INTERVAL, 30) + hass.data[DOMAIN]["interval"] = entry.data.get(CONFIG_INTERVAL, 30) client = DavisVantageClient(hass, protocol, link, rain_collector) await client.connect_to_station() static_info = await client.async_get_static_info() - firmware_version = static_info.get('version', None) if static_info is not None else None - hass.data.setdefault(DATA_ARCHIVE_PERIOD, static_info.get('archive_period', None) if static_info is not None else None) + firmware_version = ( + static_info.get("version", None) if static_info is not None else None + ) + hass.data.setdefault( + DATA_ARCHIVE_PERIOD, + static_info.get("archive_period", None) if static_info is not None else None, + ) device_info = DeviceInfo( identifiers={(DOMAIN, entry.entry_id)}, @@ -64,37 +69,38 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: name=NAME, model=entry.data.get(CONFIG_STATION_MODEL, "Unknown"), sw_version=firmware_version, - hw_version=None + hw_version=None, ) hass.data[DOMAIN][entry.entry_id] = coordinator = DavisVantageDataUpdateCoordinator( - hass=hass, client=client, device_info=device_info) + hass=hass, client=client, device_info=device_info + ) await coordinator.async_config_entry_first_refresh() await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(async_reload_entry)) - async def set_davis_time(call: ServiceCall) -> None: + async def set_davis_time(_: ServiceCall) -> None: await client.async_set_davis_time() - async def get_davis_time(call: ServiceCall) -> dict[str, Any]: + async def get_davis_time(_: ServiceCall) -> dict[str, Any]: davis_time = await client.async_get_davis_time() if davis_time is not None: return { - "davis_time": convert_to_iso_datetime(davis_time, ZoneInfo(hass.config.time_zone)) + "davis_time": convert_to_iso_datetime( + davis_time, ZoneInfo(hass.config.time_zone) + ) } else: - return { - "error": "Couldn't get davis time, please try again later" - } + return {"error": "Couldn't get davis time, please try again later"} - async def get_raw_data(call: ServiceCall) -> dict[str, Any]: + async def get_raw_data(_: ServiceCall) -> dict[str, Any]: raw_data = client.get_raw_data() json_data = safe_serialize(raw_data) return json.loads(json_data) - async def get_info(call: ServiceCall) -> dict[str, Any]: + async def get_info(_: ServiceCall) -> dict[str, Any]: info = await client.async_get_info() if info is not None: return info @@ -103,15 +109,19 @@ async def get_info(call: ServiceCall) -> dict[str, Any]: "error": "Couldn't get firmware information from Davis weather station" } + hass.services.async_register(DOMAIN, SERVICE_SET_DAVIS_TIME, set_davis_time) hass.services.async_register( - DOMAIN, SERVICE_SET_DAVIS_TIME, set_davis_time - ) - hass.services.async_register( - DOMAIN, SERVICE_GET_DAVIS_TIME, get_davis_time, supports_response=SupportsResponse.ONLY + DOMAIN, + SERVICE_GET_DAVIS_TIME, + get_davis_time, + supports_response=SupportsResponse.ONLY, ) hass.services.async_register( - DOMAIN, SERVICE_GET_RAW_DATA, get_raw_data, supports_response=SupportsResponse.ONLY + DOMAIN, + SERVICE_GET_RAW_DATA, + get_raw_data, + supports_response=SupportsResponse.ONLY, ) hass.services.async_register( @@ -119,11 +129,16 @@ async def get_info(call: ServiceCall) -> dict[str, Any]: ) def safe_serialize(obj: Any): - default = lambda o: f"<>" # type: ignore - return json.dumps(obj, default=default) # type: ignore + # default = lambda obj: f"<>" # type: ignore + default = get_default(obj) + return json.dumps(obj, default=default) # type: ignore + + def get_default(obj: Any): + return f"<>" return True + async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): diff --git a/custom_components/davis_vantage/binary_sensor.py b/custom_components/davis_vantage/binary_sensor.py index c0df96c..2a7e629 100755 --- a/custom_components/davis_vantage/binary_sensor.py +++ b/custom_components/davis_vantage/binary_sensor.py @@ -1,3 +1,4 @@ +from functools import cached_property from homeassistant.components.binary_sensor import ( DOMAIN as BINARY_SENSOR_DOMAIN, BinarySensorEntity, @@ -12,12 +13,10 @@ from .coordinator import DavisVantageDataUpdateCoordinator DESCRIPTIONS: list[BinarySensorEntityDescription] = [ - BinarySensorEntityDescription( - key="IsRaining", - translation_key="is_raining" - ) + BinarySensorEntityDescription(key="IsRaining", translation_key="is_raining") ] + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, @@ -57,11 +56,15 @@ def __init__( """Initialize Davis Vantage sensor.""" super().__init__(coordinator=coordinator) self.entity_description = description - self.entity_id = f"{BINARY_SENSOR_DOMAIN}.{DEFAULT_NAME} {KEY_TO_NAME[description.key]}".lower() - self._attr_unique_id = f"{entry_id}-{DEFAULT_NAME} {KEY_TO_NAME[description.key]}" + self.entity_id = ( + f"{BINARY_SENSOR_DOMAIN}.{DEFAULT_NAME} {KEY_TO_NAME[description.key]}".lower() + ) + self._attr_unique_id = ( + f"{entry_id}-{DEFAULT_NAME} {KEY_TO_NAME[description.key]}" + ) self._attr_device_info = coordinator.device_info - @property + @cached_property def is_on(self) -> bool | None: """Return the is_on of the sensor.""" key = self.entity_description.key diff --git a/custom_components/davis_vantage/client.py b/custom_components/davis_vantage/client.py index 1e52ac6..77f3d0a 100755 --- a/custom_components/davis_vantage/client.py +++ b/custom_components/davis_vantage/client.py @@ -1,4 +1,5 @@ """All client function""" + from typing import Any from datetime import datetime, timedelta, time, date from zoneinfo import ZoneInfo @@ -33,14 +34,10 @@ class DavisVantageClient: """Davis Vantage Client class""" - _vantagepro2: VantagePro2 = None # type: ignore + _vantagepro2: VantagePro2 = None # type: ignore def __init__( - self, - hass: HomeAssistant, - protocol: str, - link: str, - rain_collector: str + self, hass: HomeAssistant, protocol: str, link: str, rain_collector: str ) -> None: self._hass = hass self._protocol = protocol @@ -68,7 +65,7 @@ async def async_get_vantagepro2fromurl(self, url: str) -> VantagePro2 | None: return vp async def connect_to_station(self): - self._vantagepro2 = await self.async_get_vantagepro2fromurl(self.get_link()) # type: ignore + self._vantagepro2 = await self.async_get_vantagepro2fromurl(self.get_link()) # type: ignore def get_current_data( self, @@ -78,30 +75,26 @@ def get_current_data( archives = None hilows = None + if not self._vantagepro2: + self.get_vantagepro2fromurl(self.get_link()) + try: self._vantagepro2.link.open() - data = self._vantagepro2.get_current_data() # type: ignore + data = self._vantagepro2.get_current_data() except Exception as e: self._vantagepro2.link.close() raise e - - #if self._hass.data.get(DATA_ARCHIVE_PERIOD) is None: - # _LOGGER.warning("Didn't get the archive period the first time, trying again") - # try: - # static_info = self.async_get_info() - # self._hass.data.setdefault(DATA_ARCHIVE_PERIOD, static_info.get('archive_period', None)) - # except Exception as e: - # raise e try: - hilows = self._vantagepro2.get_hilows() # type: ignore + hilows = self._vantagepro2.get_hilows() except Exception: pass try: end_datetime = datetime.now() - start_datetime = end_datetime - timedelta(minutes=(self._hass.data.get(DATA_ARCHIVE_PERIOD) * 2)) # type: ignore - archives = self._vantagepro2.get_archives(start_datetime, end_datetime) # type: ignore + start_datetime = end_datetime - \ + timedelta(minutes=self._hass.data.get(DATA_ARCHIVE_PERIOD) * 2) # type: ignore + archives = self._vantagepro2.get_archives(start_datetime, end_datetime) # type: ignore except Exception: pass finally: @@ -143,8 +136,8 @@ async def async_get_current_data(self) -> LoopDataParserRevB | None: data["LastError"] = "Couldn't acquire data, no data received" except Exception as e: - _LOGGER.warning(f"Couldn't acquire data from {self.get_link()}: {e}") - data["LastError"] = f"Couldn't acquire data: {e}" + _LOGGER.error("Couldn't acquire data from %s: %s", self.get_link(), e) + data["LastError"] = f"Couldn't acquire data on {self.get_link()}: {e}" if data["LastError"]: data["LastErrorTime"] = now @@ -216,7 +209,7 @@ def get_info(self) -> dict[str, Any] | None: try: self._vantagepro2.link.open() firmware_version = self._vantagepro2.firmware_version # type: ignore - archive_period = self._vantagepro2.archive_period # type: ignore + archive_period = self._vantagepro2.archive_period # type: ignore firmware_date = self._vantagepro2.firmware_date # type: ignore diagnostics = self._vantagepro2.diagnostics # type: ignore except Exception as e: @@ -227,7 +220,7 @@ def get_info(self) -> dict[str, Any] | None: "version": firmware_version, "date": firmware_date, "diagnostics": diagnostics, - "archive_period": archive_period + "archive_period": archive_period, } async def async_get_info(self) -> dict[str, Any] | None: @@ -243,15 +236,12 @@ def get_static_info(self) -> dict[str, Any] | None: try: self._vantagepro2.link.open() firmware_version = self._vantagepro2.firmware_version # type: ignore - archive_period = self._vantagepro2.archive_period # type: ignore + archive_period = self._vantagepro2.archive_period # type: ignore except Exception as e: raise e finally: self._vantagepro2.link.close() - return { - "version": firmware_version, - "archive_period": archive_period - } + return {"version": firmware_version, "archive_period": archive_period} async def async_get_static_info(self) -> dict[str, Any] | None: info = None @@ -297,7 +287,7 @@ def convert_values(self, data: dict[str, Any]) -> None: data["RainCollector"] = self._rain_collector if data["RainCollector"] != RAIN_COLLECTOR_IMPERIAL: self.correct_rain_values(data) - data['StormStartDate'] = self.strtodate(data['StormStartDate']) + data["StormStartDate"] = self.strtodate(data["StormStartDate"]) def correct_rain_values(self, data: dict[str, Any]): if data["RainDay"] is not None: @@ -330,8 +320,8 @@ def remove_incorrect_data( for key in data.keys(): # type: ignore info_key = re.sub(r"\d+$", "", key) # type: ignore data_type = data_info.get(info_key, "") - raw_value = raw_data.get(info_key, 0) # type: ignore - if self.is_incorrect_value(raw_value, data_type): # type: ignore + raw_value = raw_data.get(info_key, 0) # type: ignore + if self.is_incorrect_value(raw_value, data_type): # type: ignore data[key] = None # type: ignore def is_incorrect_value(self, raw_value: int, data_type: str) -> bool: @@ -353,33 +343,33 @@ def add_hilows(self, hilows: HighLowParserRevB | None, data: dict[str, Any]): if not hilows: return data["TempOutHiDay"] = hilows["TempHiDay"] - data["TempOutHiTime"] = self.strtotime(hilows["TempHiTime"]) # type: ignore + data["TempOutHiTime"] = self.strtotime(hilows["TempHiTime"]) # type: ignore data["TempOutLowDay"] = hilows["TempLoDay"] - data["TempOutLowTime"] = self.strtotime(hilows["TempLoTime"]) # type: ignore + data["TempOutLowTime"] = self.strtotime(hilows["TempLoTime"]) # type: ignore data["DewPointHiDay"] = hilows["DewHiDay"] - data["DewPointHiTime"] = self.strtotime(hilows["DewHiTime"]) # type: ignore + data["DewPointHiTime"] = self.strtotime(hilows["DewHiTime"]) # type: ignore data["DewPointLowDay"] = hilows["DewLoDay"] - data["DewPointLowTime"] = self.strtotime(hilows["DewLoTime"]) # type: ignore + data["DewPointLowTime"] = self.strtotime(hilows["DewLoTime"]) # type: ignore data["RainRateDay"] = hilows["RainHiDay"] - data["RainRateTime"] = self.strtotime(hilows["RainHiTime"]) # type: ignore + data["RainRateTime"] = self.strtotime(hilows["RainHiTime"]) # type: ignore data["BarometerHiDay"] = hilows["BaroHiDay"] - data["BarometerHiTime"] = self.strtotime(hilows["BaroHiTime"]) # type: ignore + data["BarometerHiTime"] = self.strtotime(hilows["BaroHiTime"]) # type: ignore data["BarometerLowDay"] = hilows["BaroLoDay"] - data["BarometerLoTime"] = self.strtotime(hilows["BaroLoTime"]) # type: ignore + data["BarometerLoTime"] = self.strtotime(hilows["BaroLoTime"]) # type: ignore data["SolarRadDay"] = hilows["SolarHiDay"] - data["SolarRadTime"] = self.strtotime(hilows["SolarHiTime"]) # type: ignore + data["SolarRadTime"] = self.strtotime(hilows["SolarHiTime"]) # type: ignore data["UVDay"] = hilows["UVHiDay"] - data["UVTime"] = self.strtotime(hilows["UVHiTime"]) # type: ignore + data["UVTime"] = self.strtotime(hilows["UVHiTime"]) # type: ignore data["WindGustDay"] = hilows["WindHiDay"] - data["WindGustTime"] = self.strtotime(hilows["WindHiTime"]) # type: ignore + data["WindGustTime"] = self.strtotime(hilows["WindHiTime"]) # type: ignore - def get_link(self) -> str | None: + def get_link(self) -> str: """Get device link for use with vproweather.""" if self._protocol == PROTOCOL_NETWORK: return f"tcp:{self._link}" diff --git a/custom_components/davis_vantage/config_flow.py b/custom_components/davis_vantage/config_flow.py index f3bf3d7..20effe2 100755 --- a/custom_components/davis_vantage/config_flow.py +++ b/custom_components/davis_vantage/config_flow.py @@ -54,10 +54,10 @@ def __init__(self, hass: HomeAssistant) -> None: async def authenticate(self, protocol: str, link: str) -> bool: """Test if we can find data for the given link.""" - _LOGGER.info(f"authenticate called") + _LOGGER.info("authenticate called") client = DavisVantageClient(self._hass, protocol, link, "") await client.connect_to_station() - return await client.async_get_davis_time() != None + return (await client.async_get_davis_time()) is not None async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]: @@ -109,12 +109,12 @@ async def async_step_setup_serial( for port in ports } - STEP_USER_DATA_SCHEMA = vol.Schema( + step_user_data_schema = vol.Schema( {vol.Required(CONFIG_LINK): vol.In(list_of_ports)} ) return self.async_show_form( - step_id="setup_serial", data_schema=STEP_USER_DATA_SCHEMA + step_id="setup_serial", data_schema=step_user_data_schema ) async def async_step_setup_network( @@ -124,10 +124,10 @@ async def async_step_setup_network( self.link = user_input[CONFIG_LINK] return await self.async_step_setup_other_info() - STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONFIG_LINK): str}) + step_user_data_schema = vol.Schema({vol.Required(CONFIG_LINK): str}) return self.async_show_form( - step_id="setup_network", data_schema=STEP_USER_DATA_SCHEMA + step_id="setup_network", data_schema=step_user_data_schema ) async def async_step_setup_other_info( @@ -151,7 +151,7 @@ async def async_step_setup_other_info( else: return self.async_create_entry(title=info["title"], data=user_input) - STEP_USER_DATA_SCHEMA = vol.Schema( + step_user_data_schema = vol.Schema( { vol.Required(CONFIG_STATION_MODEL): vol.In( [MODEL_VANTAGE_PRO2, MODEL_VANTAGE_PRO2PLUS] @@ -166,7 +166,7 @@ async def async_step_setup_other_info( ) return self.async_show_form( - step_id="setup_other_info", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + step_id="setup_other_info", data_schema=step_user_data_schema, errors=errors ) async def async_step_reconfigure( diff --git a/custom_components/davis_vantage/const.py b/custom_components/davis_vantage/const.py index e2a09b1..a127d42 100755 --- a/custom_components/davis_vantage/const.py +++ b/custom_components/davis_vantage/const.py @@ -3,7 +3,7 @@ NAME = "Davis Vantage" DOMAIN = "davis_vantage" MANUFACTURER = "Davis" -VERSION = "1.3.0" +VERSION = "1.3.1" DEFAULT_SYNC_INTERVAL = 30 # seconds DEFAULT_NAME = NAME @@ -100,4 +100,4 @@ "HumExtra06": "Extra Humidity 6", "HumExtra07": "Extra Humidity 7", "IsRaining": "Is Raining" -} \ No newline at end of file +} diff --git a/custom_components/davis_vantage/coordinator.py b/custom_components/davis_vantage/coordinator.py index b68b9f1..f2dbdfa 100755 --- a/custom_components/davis_vantage/coordinator.py +++ b/custom_components/davis_vantage/coordinator.py @@ -3,7 +3,7 @@ import logging from homeassistant.helpers.update_coordinator import UpdateFailed, DataUpdateCoordinator -from homeassistant.helpers.entity import DeviceInfo +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.core import HomeAssistant from pyvantagepro.parser import LoopDataParserRevB @@ -18,8 +18,6 @@ class DavisVantageDataUpdateCoordinator(DataUpdateCoordinator): """Class to manage fetching data from the weather station.""" - data: LoopDataParserRevB - def __init__( self, hass: HomeAssistant, client: DavisVantageClient, device_info: DeviceInfo ) -> None: diff --git a/custom_components/davis_vantage/manifest.json b/custom_components/davis_vantage/manifest.json index e589ff6..b836ac6 100755 --- a/custom_components/davis_vantage/manifest.json +++ b/custom_components/davis_vantage/manifest.json @@ -12,6 +12,6 @@ "issue_tracker": "https://github.com/MarcoGos/davis_vantage/issues", "requirements": ["PyVantagePro-MarcoGos==0.3.13"], "ssdp": [], - "version": "1.3.0", + "version": "1.3.1", "zeroconf": [] } diff --git a/custom_components/davis_vantage/sensor.py b/custom_components/davis_vantage/sensor.py index ec0f0ec..0897873 100755 --- a/custom_components/davis_vantage/sensor.py +++ b/custom_components/davis_vantage/sensor.py @@ -1,10 +1,15 @@ import logging +from functools import cached_property + from homeassistant.components.sensor import ( - DOMAIN as SENSOR_DOMAIN, SensorEntity, - SensorEntityDescription, + SensorEntityDescription +) +from homeassistant.components.sensor.const import ( + DOMAIN as SENSOR_DOMAIN, SensorDeviceClass ) + from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( PERCENTAGE, @@ -16,10 +21,10 @@ UnitOfTemperature, UnitOfPressure, UnitOfIrradiance, - UnitOfTime + UnitOfTime, + EntityCategory ) from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity import EntityCategory from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.typing import StateType from homeassistant.helpers.update_coordinator import CoordinatorEntity @@ -33,7 +38,6 @@ MODEL_VANTAGE_PRO2PLUS, KEY_TO_NAME ) - from .coordinator import DavisVantageDataUpdateCoordinator _LOGGER: logging.Logger = logging.getLogger(__package__) @@ -621,7 +625,7 @@ def __init__( self._attr_unique_id = f"{entry_id}-{DEFAULT_NAME} {KEY_TO_NAME[description.key]}" self._attr_device_info = coordinator.device_info - @property + @cached_property def native_value(self) -> StateType: """Return the state of the sensor.""" key = self.entity_description.key