From c3107d65035c0a35e5a0bb829d89f7b288211451 Mon Sep 17 00:00:00 2001 From: Jordan Date: Wed, 10 Jan 2024 19:58:57 -0500 Subject: [PATCH] Update aiophyn version and modify the shutoff switch to a valve --- custom_components/phyn/__init__.py | 2 +- custom_components/phyn/device.py | 38 ++-- custom_components/phyn/devices/pp.py | 89 +++++---- custom_components/phyn/devices/pp2.py | 175 ------------------ custom_components/phyn/manifest.json | 4 +- .../phyn/{switch.py => valve.py} | 6 +- hacs.json | 2 +- 7 files changed, 77 insertions(+), 239 deletions(-) delete mode 100644 custom_components/phyn/devices/pp2.py rename custom_components/phyn/{switch.py => valve.py} (87%) diff --git a/custom_components/phyn/__init__.py b/custom_components/phyn/__init__.py index 14b36f1..cc62a8d 100644 --- a/custom_components/phyn/__init__.py +++ b/custom_components/phyn/__init__.py @@ -17,7 +17,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.SENSOR, Platform.SWITCH] +PLATFORMS = [Platform.SENSOR, Platform.VALVE] async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: diff --git a/custom_components/phyn/device.py b/custom_components/phyn/device.py index 1b2ac9e..cd2df36 100644 --- a/custom_components/phyn/device.py +++ b/custom_components/phyn/device.py @@ -8,12 +8,11 @@ from aiophyn.errors import RequestError from async_timeout import timeout -from .exceptions import HaAuthError, HaCannotConnect - from homeassistant.core import HomeAssistant from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed import homeassistant.util.dt as dt_util +from .exceptions import HaAuthError, HaCannotConnect from .const import DOMAIN as PHYN_DOMAIN, LOGGER from .devices.pp import ( @@ -21,13 +20,11 @@ PhynDailyUsageSensor, PhynConsumptionSensor, PhynCurrentFlowRateSensor, - PhynSwitch, + PhynValve, PhynTemperatureSensor, PhynPressureSensor ) -import json - class PhynDeviceDataUpdateCoordinator(DataUpdateCoordinator): """Phyn device object.""" @@ -46,6 +43,7 @@ def __init__( self._device_state: dict[str, Any] = {} self._rt_device_state: dict[str, Any] = {} self._water_usage: dict[str, Any] = {} + self._last_known_valve_state: bool = True if product_code in ['PP1','PP2']: # Entities for Phyn Plus 1 and Phyn Plus 2 @@ -56,8 +54,7 @@ def __init__( PhynConsumptionSensor(self), PhynTemperatureSensor(self), PhynPressureSensor(self), - - PhynSwitch(self), + PhynValve(self), ] super().__init__( @@ -120,6 +117,8 @@ def current_flow_rate(self) -> float: if round(self._rt_device_state['flow']['v'], 2) == 0: return 0.0 return round(self._rt_device_state['flow']['v'], 3) + if "flow" not in self._device_state: + return None return round(self._device_state["flow"]["mean"], 3) @property @@ -127,6 +126,8 @@ def current_psi(self) -> float: """Return the current pressure in psi.""" if "sensor_data" in self._rt_device_state: return round(self._rt_device_state['sensor_data']['pressure']['v'], 2) + if "sensor_data" not in self._device_state: + return None return round(self._device_state["pressure"]["mean"], 2) @property @@ -134,6 +135,8 @@ def temperature(self) -> float: """Return the current temperature in degrees F.""" if "sensor_data" in self._rt_device_state: return round(self._rt_device_state['sensor_data']['temperature']['v'], 2) + if "sensor_data" not in self._device_state: + return None return round(self._device_state["temperature"]["mean"], 2) @property @@ -159,14 +162,27 @@ def serial_number(self) -> str: return self._device_state["serial_number"] @property - def valve_state(self) -> str: + def valve_open(self) -> bool: """Return the valve state for the device.""" + if self.valve_changing: + return self._last_known_valve_state if "sov_state" in self._rt_device_state: - return self._rt_device_state["sov_state"] + self._last_known_valve_state = self._rt_device_state["sov_state"] == "Open" + return self._rt_device_state["sov_state"] == "Open" if "sov_status" in self._device_state: - return self._device_state["sov_status"]["v"] + self._last_known_valve_state = self._device_state["sov_status"]["v"] == "Open" + return self._device_state["sov_status"]["v"] == "Open" return None + @property + def valve_changing(self) -> bool: + """Return the valve changing status""" + if "sov_state" in self._rt_device_state: + return self._rt_device_state["sov_state"] == "Partial" + if "sov_status" in self._device_state: + return self._device_state["sov_status"]["v"] == "Partial" + return False + async def async_setup(self): """Setup a new device coordinator""" LOGGER.debug("Setting up coordinator") @@ -191,9 +207,7 @@ async def _update_consumption_data(self, *_) -> None: LOGGER.debug("Updated Phyn consumption data: %s", self._water_usage) async def on_device_update(self, device_id, data): - #LOGGER.debug("Received new data: %s" % json.dumps(data, indent=2)) if device_id == self._phyn_device_id: self._rt_device_state = data for entity in self.entities: - #LOGGER.debug(f"Updating {entity} ({entity.unique_id}, {entity.entity_id})") entity.async_write_ha_state() diff --git a/custom_components/phyn/devices/pp.py b/custom_components/phyn/devices/pp.py index 91fa198..0eea803 100644 --- a/custom_components/phyn/devices/pp.py +++ b/custom_components/phyn/devices/pp.py @@ -1,21 +1,17 @@ """Support for Phyn Plus Water Monitor sensors.""" from __future__ import annotations -from homeassistant.components.binary_sensor import ( - BinarySensorEntity, - BinarySensorDeviceClass, -) - from homeassistant.components.sensor import ( SensorDeviceClass, SensorEntity, SensorStateClass, ) -from homeassistant.components.switch import SwitchEntity -from homeassistant.core import callback -from homeassistant.config_entries import ConfigEntry +from homeassistant.components.valve import ( + ValveDeviceClass, + ValveEntity, + ValveEntityFeature +) from homeassistant.const import ( - PERCENTAGE, UnitOfPressure, UnitOfTemperature, UnitOfVolume, @@ -106,54 +102,57 @@ def native_value(self) -> float | None: """Return the current flow rate.""" if self._device.current_flow_rate is None: return None - return round(self._device.current_flow_rate, 1) + return round(self._device.current_flow_rate, 1) -class PhynSwitch(PhynEntity, SwitchEntity): - """Switch class for the Phyn valve.""" +class PhynValve(PhynEntity, ValveEntity): + """ValveEntity for the Phyn valve.""" def __init__(self, device) -> None: - """Initialize the Phyn switch.""" + """Initialize the Phyn Valve.""" super().__init__("shutoff_valve", "Shutoff valve", device) - self._state = self._device.valve_state == "Open" - - @property - def is_on(self) -> bool: - """Return True if the valve is open. Keep state for transition""" - if self._device.valve_state == "Partial": - return self._state - self._state = self._device.valve_state == "Open" - return self._state - - @property - def icon(self): - """Return the icon to use for the valve.""" - if self.is_on: - return "mdi:valve-open" - return "mdi:valve-closed" - - async def async_turn_on(self, **kwargs: Any) -> None: + self._attr_supported_features = ValveEntityFeature(ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE) + self._attr_device_class = ValveDeviceClass.WATER + self._attr_reports_position = False + self._last_known_state: bool = False + + async def async_open_valve(self) -> None: """Open the valve.""" await self._device.api_client.device.open_valve(self._device.id) - #self._state = True - #self.async_write_ha_state() - async def async_turn_off(self, **kwargs: Any) -> None: + def open_valve(self) -> None: + """Open the valve.""" + raise NotImplementedError() + + async def async_close_valve(self) -> None: """Close the valve.""" await self._device.api_client.device.close_valve(self._device.id) - #self._state = False - #self.async_write_ha_state() - @callback - def async_update_state(self) -> None: - """Retrieve the latest valve state and update the state machine.""" - self._state = self._device.valve_state == "Open" - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - self.async_on_remove(self._device.async_add_listener(self.async_update_state)) + def close_valve(self) -> None: + """Close valve.""" + raise NotImplementedError() + + @property + def _attr_is_closed(self) -> bool | None: + """ Is the valve closed """ + if self._device.valve_open is None: + return None + self._last_known_state = self._device.valve_open + return not self._device.valve_open + + @property + def _attr_is_opening(self) -> bool: + """ Is the valve opening """ + if self._device.valve_changing and self._device._last_known_valve_state is False: + return True + return False + @property + def _attr_is_closing(self) -> bool: + """ Is the valve closing """ + if self._device.valve_changing and self._device._last_known_valve_state is True: + return True + return False class PhynTemperatureSensor(PhynEntity, SensorEntity): diff --git a/custom_components/phyn/devices/pp2.py b/custom_components/phyn/devices/pp2.py deleted file mode 100644 index cd1008b..0000000 --- a/custom_components/phyn/devices/pp2.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Support for Phyn Water Monitor sensors.""" -from __future__ import annotations - -from homeassistant.components.binary_sensor import ( - BinarySensorEntity, - BinarySensorDeviceClass, -) - -from homeassistant.components.sensor import ( - SensorDeviceClass, - SensorEntity, - SensorStateClass, -) -from homeassistant.components.switch import SwitchEntity -from homeassistant.core import callback -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ( - PERCENTAGE, - UnitOfPressure, - UnitOfTemperature, - UnitOfVolume, -) - -from ..entity import PhynEntity - -WATER_ICON = "mdi:water" -GAUGE_ICON = "mdi:gauge" -NAME_DAILY_USAGE = "Daily water usage" -NAME_FLOW_RATE = "Current water flow rate" -NAME_WATER_TEMPERATURE = "Current water temperature" -NAME_WATER_PRESSURE = "Current water pressure" - -class PhynFlowState(PhynEntity, BinarySensorEntity): - - _attr_device_class = BinarySensorDeviceClass.MOVING - - def __init__(self, device): - """Initialize the daily water usage sensor.""" - super().__init__("water_flow_state", "Water Flowing", device) - self._state: bool = None - - @property - def is_on(self) -> bool | None: - if "flow_state" in self._device._rt_device_state: - if self._device._rt_device_state['flow_state']['v'] == "off": - return False - return True - return None - -class PhynDailyUsageSensor(PhynEntity, SensorEntity): - """Monitors the daily water usage.""" - - _attr_icon = WATER_ICON - _attr_native_unit_of_measurement = UnitOfVolume.GALLONS - _attr_state_class: SensorStateClass = SensorStateClass.TOTAL_INCREASING - _attr_device_class = SensorDeviceClass.WATER - - def __init__(self, device): - """Initialize the daily water usage sensor.""" - super().__init__("daily_consumption", NAME_DAILY_USAGE, device) - self._state: float = None - - @property - def native_value(self) -> float | None: - """Return the current daily usage.""" - if self._device.consumption_today is None: - return None - return round(self._device.consumption_today, 1) - - -class PhynCurrentFlowRateSensor(PhynEntity, SensorEntity): - """Monitors the current water flow rate.""" - - _attr_native_unit_of_measurement = "gpm" - _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT - _attr_translation_key = "current_flow_rate" - - def __init__(self, device): - """Initialize the flow rate sensor.""" - super().__init__("current_flow_rate", NAME_FLOW_RATE, device) - self._state: float = None - - @property - def native_value(self) -> float | None: - """Return the current flow rate.""" - if self._device.current_flow_rate is None: - return None - return round(self._device.current_flow_rate, 1) - - -class PhynSwitch(PhynEntity, SwitchEntity): - """Switch class for the Phyn valve.""" - - def __init__(self, device) -> None: - """Initialize the Phyn switch.""" - super().__init__("shutoff_valve", "Shutoff valve", device) - self._state = self._device.valve_state == "Open" - - @property - def is_on(self) -> bool: - """Return True if the valve is open. Keep state for transition""" - if self._device.valve_state == "Partial": - return self._state - self._state = self._device.valve_state == "Open" - return self._state - - @property - def icon(self): - """Return the icon to use for the valve.""" - if self.is_on: - return "mdi:valve-open" - return "mdi:valve-closed" - - async def async_turn_on(self, **kwargs: Any) -> None: - """Open the valve.""" - await self._device.api_client.device.open_valve(self._device.id) - #self._state = True - #self.async_write_ha_state() - - async def async_turn_off(self, **kwargs: Any) -> None: - """Close the valve.""" - await self._device.api_client.device.close_valve(self._device.id) - #self._state = False - #self.async_write_ha_state() - - @callback - def async_update_state(self) -> None: - """Retrieve the latest valve state and update the state machine.""" - self._state = self._device.valve_state == "Open" - self.async_write_ha_state() - - async def async_added_to_hass(self) -> None: - """When entity is added to hass.""" - self.async_on_remove(self._device.async_add_listener(self.async_update_state)) - - - -class PhynTemperatureSensor(PhynEntity, SensorEntity): - """Monitors the temperature.""" - - _attr_device_class = SensorDeviceClass.TEMPERATURE - _attr_native_unit_of_measurement = UnitOfTemperature.FAHRENHEIT - _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT - - def __init__(self, device): - """Initialize the temperature sensor.""" - super().__init__("temperature", NAME_WATER_TEMPERATURE, device) - self._state: float = None - - @property - def native_value(self) -> float | None: - """Return the current temperature.""" - if self._device.temperature is None: - return None - return round(self._device.temperature, 1) - - -class PhynPressureSensor(PhynEntity, SensorEntity): - """Monitors the water pressure.""" - - _attr_device_class = SensorDeviceClass.PRESSURE - _attr_native_unit_of_measurement = UnitOfPressure.PSI - _attr_state_class: SensorStateClass = SensorStateClass.MEASUREMENT - - def __init__(self, device): - """Initialize the pressure sensor.""" - super().__init__("water_pressure", NAME_WATER_PRESSURE, device) - self._state: float = None - - @property - def native_value(self) -> float | None: - """Return the current water pressure.""" - if self._device.current_psi is None: - return None - return round(self._device.current_psi, 1) diff --git a/custom_components/phyn/manifest.json b/custom_components/phyn/manifest.json index fd8aebd..956ad96 100644 --- a/custom_components/phyn/manifest.json +++ b/custom_components/phyn/manifest.json @@ -4,10 +4,10 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/phyn", "requirements": [ - "aiophyn @ git+https://github.com/jordanruthe/aiophyn@dev" + "git+https://github.com/jordanruthe/aiophyn.git@main#aiophyn==2024.1.0" ], "codeowners": ["@mizterb","@jordanruthe"], "iot_class": "cloud_polling", "loggers": ["aiophyn"], - "version": "2023.09.0" + "version": "2024.01.0" } diff --git a/custom_components/phyn/switch.py b/custom_components/phyn/valve.py similarity index 87% rename from custom_components/phyn/switch.py rename to custom_components/phyn/valve.py index 4c9bfc4..1d38cd3 100644 --- a/custom_components/phyn/switch.py +++ b/custom_components/phyn/valve.py @@ -5,7 +5,7 @@ import voluptuous as vol -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.valve import ValveEntity from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers import entity_platform @@ -14,7 +14,7 @@ from .const import DOMAIN as PHYN_DOMAIN from .device import PhynDeviceDataUpdateCoordinator -from .devices.pp import PhynSwitch +from .devices.pp import PhynValve async def async_setup_entry( @@ -32,6 +32,6 @@ async def async_setup_entry( entities.extend([ entity for entity in device.entities - if isinstance(entity, SwitchEntity) + if isinstance(entity, ValveEntity) ]) async_add_entities(entities) diff --git a/hacs.json b/hacs.json index 48cd8cb..265b7a0 100644 --- a/hacs.json +++ b/hacs.json @@ -2,5 +2,5 @@ "name": "Phyn Smart Water Assistant", "content_in_root": false, "render_readme": true, - "homeassistant": "2023.1.0" + "homeassistant": "2024.1.0" }