Skip to content

Commit

Permalink
Merge pull request #24 from jordanruthe/phyn_classic_support
Browse files Browse the repository at this point in the history
Phyn classic support
  • Loading branch information
jordanruthe authored Jan 27, 2024
2 parents 0da6348 + 7b6c668 commit 9dfc7d5
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 43 deletions.
3 changes: 1 addition & 2 deletions custom_components/phyn/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = PhynDataUpdateCoordinator(hass, client)
for home in homes:
for device in home["devices"]:
if device["product_code"] in ["PW1","PP1","PP2"]:
coordinator.add_device(home["id"], device["device_id"], device["product_code"])
coordinator.add_device(home["id"], device["device_id"], device["product_code"])
hass.data[DOMAIN][entry.entry_id]["coordinator"] = coordinator

await coordinator.async_refresh()
Expand Down
148 changes: 148 additions & 0 deletions custom_components/phyn/devices/pc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
"""Support for Phyn Classic Water Monitor sensors."""
from __future__ import annotations
from typing import Any

from aiophyn.errors import RequestError
from async_timeout import timeout

from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.components.valve import (
ValveDeviceClass,
ValveEntity,
ValveEntityFeature
)
from homeassistant.const import (
UnitOfPressure,
UnitOfTemperature,
UnitOfVolume,
)

from homeassistant.helpers.update_coordinator import UpdateFailed
import homeassistant.util.dt as dt_util

from ..const import LOGGER
from ..entities.base import (
PhynDailyUsageSensor,
PhynFirmwareUpdateAvailableSensor,
PhynFirwmwareUpdateEntity,
PhynTemperatureSensor,
PhynPressureSensor,
)
from .base import PhynDevice

class PhynClassicDevice(PhynDevice):
"""Phyn device object."""

def __init__(
self, coordinator, home_id: str, device_id: str, product_code: str
) -> None:
"""Initialize the device."""
super().__init__ (coordinator, home_id, device_id, product_code)
self._device_state: dict[str, Any] = {
"cold_line_num": None,
"hot_line_num": None,
}
self._away_mode: dict[str, Any] = {}
self._water_usage: dict[str, Any] = {}
self._last_known_valve_state: bool = True

self.entities = [
PhynDailyUsageSensor(self),
PhynFirmwareUpdateAvailableSensor(self),
PhynFirwmwareUpdateEntity(self),
PhynTemperatureSensor(self, "temperature1", "Average hot water temperature", "temperature1"),
PhynTemperatureSensor(self, "temperature2", "Average cold water temperature", "temperature2"),
PhynPressureSensor(self, "pressure1", "Average hot water pressure", "current_psi1"),
PhynPressureSensor(self, "pressure2", "Average cold water pressure", "current_psi2"),
]

async def async_update_data(self):
"""Update data via library."""
try:
async with timeout(20):
await self._update_device_state()
await self._update_consumption_data()

#Update every hour
if self._update_count % 60 == 0:
await self._update_firmware_information()

self._update_count += 1
except (RequestError) as error:
raise UpdateFailed(error) from error

@property
def cold_line_num(self) -> int | None:
"""Return cold line number"""
return self._device_state['cold_line_num']

@property
def consumption_today(self) -> float:
"""Return the current consumption for today in gallons."""
return self._water_usage["water_consumption"]

@property
def current_flow_rate(self) -> float:
"""Return current flow rate in gpm."""
if "v" not in self._device_state["flow"]:
return None
return round(self._device_state["flow"]["v"], 3)

@property
def current_psi1(self) -> float:
"""Return the current pressure in psi."""
if "v" in self._device_state["pressure1"]:
return round(self._device_state["pressure1"]["v"], 2)
return round(self._device_state["pressure1"]["mean"], 2)

@property
def current_psi2(self) -> float:
"""Return the current pressure in psi."""
if "v" in self._device_state["pressure2"]:
return round(self._device_state["pressure2"]["v"], 2)
return round(self._device_state["pressure2"]["mean"], 2)

@property
def hot_line_num(self) -> int | None:
"""Return hot line number"""
return self._device_state['hot_line_num']

@property
def leak_test_running(self) -> bool:
"""Check if a leak test is running"""
return self._device_state["sov_status"]["v"] == "LeakExp"

@property
def temperature1(self) -> float:
"""Return the current temperature in degrees F."""
if "v" in self._device_state["temperature1"]:
return round(self._device_state["temperature1"]["v"], 2)
return round(self._device_state["temperature1"]["mean"], 2)

@property
def temperature2(self) -> float:
"""Return the current temperature in degrees F."""
if "v" in self._device_state["temperature2"]:
return round(self._device_state["temperature2"]["v"], 2)
return round(self._device_state["temperature2"]["mean"], 2)

async def _update_consumption_data(self, *_) -> None:
"""Update water consumption data from the API."""
today = dt_util.now().date()
duration = today.strftime("%Y/%m/%d")
self._water_usage = await self._coordinator.api_client.device.get_consumption(
self._phyn_device_id, duration
)
LOGGER.debug("Updated Phyn consumption data: %s", self._water_usage)

async def async_setup(self):
"""Async setup not needed"""
return None
44 changes: 3 additions & 41 deletions custom_components/phyn/devices/pp.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@
from ..const import GPM_TO_LPM, LOGGER, UnitOfVolumeFlow
from ..entities.base import (
PhynEntity,
PhynDailyUsageSensor,
PhynFirmwareUpdateAvailableSensor,
PhynFirwmwareUpdateEntity,
PhynPressureSensor,
PhynTemperatureSensor,
PhynSwitchEntity
)
Expand Down Expand Up @@ -76,7 +78,7 @@ def __init__(
PhynLeakTestSensor(self),
PhynScheduledLeakTestEnabledSwitch(self),
PhynTemperatureSensor(self, "temperature", NAME_WATER_TEMPERATURE),
PhynPressureSensor(self),
PhynPressureSensor(self, "pressure", NAME_WATER_PRESSURE),
PhynValve(self),
]

Expand Down Expand Up @@ -325,26 +327,6 @@ def icon(self):
return "mdi:bag-suitcase"
return "mdi:home-circle"

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 PhynConsumptionSensor(PhynEntity, SensorEntity):
"""Monitors the amount of water usage."""

Expand Down Expand Up @@ -442,23 +424,3 @@ def _attr_is_closing(self) -> bool:
if self._device.valve_changing and self._device._last_known_valve_state is True:
return True
return False


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)
44 changes: 44 additions & 0 deletions custom_components/phyn/entities/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@

from ..const import DOMAIN as PHYN_DOMAIN

WATER_ICON = "mdi:water"
NAME_DAILY_USAGE = "Daily water usage"

class PhynEntity(Entity):
"""A base class for Phyn entities."""

Expand Down Expand Up @@ -88,6 +91,26 @@ def is_on(self) -> bool | None:
return getattr(self._device, self._device_property)
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 PhynFirmwareUpdateAvailableSensor(PhynEntity, BinarySensorEntity):
"""Firmware Update Available Sensor"""
_attr_device_class = BinarySensorDeviceClass.UPDATE
Expand Down Expand Up @@ -183,6 +206,27 @@ def native_value(self) -> float | None:
return None
return round(self._device.humidity, 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, name, readable_name, device_property = None):
"""Initialize the pressure sensor."""
super().__init__(name, readable_name, device)
self._state: float = None
self._device_property = device_property

@property
def native_value(self) -> float | None:
"""Return the current water pressure."""
if self._device_property is not None and hasattr(self._device, self._device_property):
return getattr(self._device, self._device_property)
if not hasattr(self._device, "current_psi") or self._device.current_psi is None:
return None
return round(self._device.current_psi, 1)

class PhynTemperatureSensor(PhynEntity, SensorEntity):
"""Monitors the temperature."""
Expand Down
5 changes: 5 additions & 0 deletions custom_components/phyn/update_coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .const import DOMAIN as PHYN_DOMAIN, LOGGER


from .devices.pc import PhynClassicDevice
from .devices.pp import PhynPlusDevice
from .devices.pw import PhynWaterSensorDevice

Expand All @@ -40,6 +41,10 @@ def add_device(self, home_id, device_id, product_code):
self._devices.append(
PhynPlusDevice(self, home_id, device_id, product_code)
)
elif product_code in ["PC1"]:
self._devices.append(
PhynClassicDevice(self, home_id, device_id, product_code)
)
elif product_code in ["PW1"]:
self._devices.append(
PhynWaterSensorDevice(self, home_id, device_id, product_code)
Expand Down

0 comments on commit 9dfc7d5

Please sign in to comment.