Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jordanruthe/homeassistant-phyn
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2024.1.3
Choose a base ref
...
head repository: jordanruthe/homeassistant-phyn
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref

Commits on Jan 24, 2024

  1. Copy the full SHA
    bb99fcb View commit details

Commits on Jan 27, 2024

  1. Updated current water flow sensor so that the measurement unit can be…

    … changed (#18)
    
    * Added device class (water)
    * Allow ability to return metric or imperial units (GPM or l/m).
    
    ---------
    
    Co-authored-by: jordanruthe <31575189+jordanruthe@users.noreply.github.com>
    timbba80 and jordanruthe authored Jan 27, 2024
    Copy the full SHA
    2ca40c2 View commit details
  2. Update README.md Configuration and Known issues (#16)

    Update README.md Configuration and Known issues. Credit to most goes to opq8
    Hedda authored Jan 27, 2024
    Copy the full SHA
    0da6348 View commit details
  3. Phyn Smart Water Assistant Support

    kasik96 authored and jordanruthe committed Jan 27, 2024
    Copy the full SHA
    e538fc2 View commit details
  4. Add PhynClassic device

    jordanruthe committed Jan 27, 2024
    Copy the full SHA
    8b4b107 View commit details
  5. Fixed some bugs

    kasik96 committed Jan 27, 2024
    Copy the full SHA
    ff59c37 View commit details
  6. Merge pull request #23 from kasik96/phyn_classic_support_edits

    Phyn Classic - Bug Fixes
    jordanruthe authored Jan 27, 2024
    Copy the full SHA
    7b6c668 View commit details
  7. Merge pull request #24 from jordanruthe/phyn_classic_support

    Phyn classic support
    jordanruthe authored Jan 27, 2024
    Copy the full SHA
    9dfc7d5 View commit details

Commits on Jan 30, 2024

  1. Copy the full SHA
    9a2041d View commit details

Commits on Feb 24, 2024

  1. fix: empty value error

    shawnheide authored and jordanruthe committed Feb 24, 2024
    Copy the full SHA
    5212f52 View commit details
  2. Copy the full SHA
    f1fb44a View commit details

Commits on Sep 7, 2024

  1. Update README.md with repo URL to fork by jordanruthe (#11)

    Update README.md with repo URL to fork by jordanruthe
    Hedda authored Sep 7, 2024
    Copy the full SHA
    ef94848 View commit details

Commits on Oct 1, 2024

  1. Add HACS validation

    jordanruthe authored Oct 1, 2024
    Copy the full SHA
    fcc861f View commit details
  2. Update manifest.json

    Include issue tracker
    jordanruthe authored Oct 1, 2024
    Copy the full SHA
    e490352 View commit details
  3. Copy the full SHA
    52a1d3d View commit details
  4. Copy the full SHA
    2382a0b View commit details

Commits on Oct 2, 2024

  1. Bump version

    jordanruthe committed Oct 2, 2024
    Copy the full SHA
    a55009a View commit details
  2. Copy the full SHA
    84e8fc2 View commit details

Commits on Jan 5, 2025

  1. Fix blocking call and fix #35

    jordanruthe committed Jan 5, 2025
    Copy the full SHA
    c382aee View commit details

Commits on Jan 6, 2025

  1. Copy the full SHA
    dbd075d View commit details
  2. Copy the full SHA
    329670c View commit details
  3. Copy the full SHA
    b91ee00 View commit details

Commits on Jan 7, 2025

  1. Add authencation error on login issue.

    Add config flow for reauthentication.
    jordanruthe committed Jan 7, 2025
    Copy the full SHA
    518c965 View commit details

Commits on Jan 8, 2025

  1. Copy the full SHA
    02c58c5 View commit details

Commits on Jan 9, 2025

  1. Copy the full SHA
    9d33711 View commit details
24 changes: 24 additions & 0 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Validate

on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:

jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v4"
- uses: "home-assistant/actions/hassfest@master"
validate-hacs:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v3"
- name: HACS validation
uses: "hacs/action@main"
with:
category: "integration"

16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -19,15 +19,27 @@ This custom component can be integrated into [HACS](https://github.com/hacs/inte

3. Click the three dots in the upper right-hand corner and select _Custom Repositories_.

4. Paste "https://github.com/MizterB/homeassistant-phyn" into _Repository_, select "Integration" as _Category_, and click Add.
4. Paste "https://github.com/jordanruthe/homeassistant-phyn" into _Repository_, select "Integration" as _Category_, and click Add.

5. Close the Custom repositories dialog after it updates with the new integration.

6. "Phyn Smart Water Assistant" will appear in your list of repositories. Click to open, click the following Download buttons.

# Configuration

Configuration is done via the UI. Add the "Phyn" integration via the Integration settings, then provide the Phyn username and password in the configuration dialog.
Configuration is done via the UI. Add the "Phyn" integration via the Integration settings and provide existing Phyn username and password.

* In the Home Assistant UI, go to Settings > Devices & services, go to the Devices tab, and click "+ Add Device" on the bottom right.

* Search for and select "Phyn".

* A prompt will appear for you to enter your Phyn Account username and password. (This could sometimes take 2-3 minutes, or longer).

# Known Issues

* Phyn home name (in the Phyn App > Settings > Home > Address > Home Name) cannot be set to "Home" or integration configuration and setup will fail.

* If get an (API) error when trying to first initialize saying "User Not Found" then take note that Phyn username e-mail address is case sensitive.

## Changelog

57 changes: 46 additions & 11 deletions custom_components/phyn/__init__.py
Original file line number Diff line number Diff line change
@@ -4,36 +4,70 @@

from aiophyn import async_get_api
from aiophyn.errors import RequestError
from botocore.exceptions import ClientError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady
)
from homeassistant.helpers.aiohttp_client import async_get_clientsession

from .const import CLIENT, DOMAIN
from .update_coordinator import PhynDataUpdateCoordinator
from .exceptions import HaAuthError, HaCannotConnect
from .services import phyn_leak_test_service_setup

_LOGGER = logging.getLogger(__name__)

PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH, Platform.UPDATE, Platform.VALVE]

async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s.%s", config_entry.version, config_entry.minor_version)

if config_entry.version > 1:
# This means the user has downgraded from a future version
return False

if config_entry.version == 1:
new = {**config_entry.data}
if config_entry.minor_version < 2:
if "Brand" not in new:
new['Brand'] = "phyn"

config_entry.version = 1
config_entry.minor_version = 2
hass.config_entries.async_update_entry(config_entry, data=new)

_LOGGER.debug("Migration to version %s.%s successful", config_entry.version, config_entry.minor_version)

return True

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up flo from a config entry."""
session = async_get_clientsession(hass)
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {}
hass.data[DOMAIN] = {}
client_id = f"homeassistant-{hass.data['core.uuid']}"
try:
hass.data[DOMAIN][entry.entry_id][CLIENT] = client = await async_get_api(
hass.data[DOMAIN][CLIENT] = client = await async_get_api(
entry.data[CONF_USERNAME], entry.data[CONF_PASSWORD],
phyn_brand=entry.data["Brand"].lower(), session=session,
client_id=client_id
)
except RequestError as err:
raise ConfigEntryNotReady from err
except RequestError as error:
raise ConfigEntryNotReady from error
except ClientError as error:
if error.response['Error']['Code'] == "NotAuthorizedException":
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="auth_failed"
)
else:
raise error

homes = await client.home.get_homes(entry.data[CONF_USERNAME])

@@ -47,23 +81,24 @@ 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"])
hass.data[DOMAIN][entry.entry_id]["coordinator"] = coordinator
coordinator.add_device(home["id"], device["device_id"], device["product_code"])
hass.data[DOMAIN]["coordinator"] = coordinator

await coordinator.async_refresh()
await coordinator.async_setup()

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
await phyn_leak_test_service_setup(hass)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
client = hass.data[DOMAIN][entry.entry_id][CLIENT]
client = hass.data[DOMAIN][CLIENT]
await client.mqtt.disconnect_and_wait()
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
del hass.data[DOMAIN][CLIENT]
del hass.data[DOMAIN]["coordinator"]
return unload_ok
2 changes: 1 addition & 1 deletion custom_components/phyn/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,7 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Phyn switches from config entry."""
coordinator = hass.data[PHYN_DOMAIN][config_entry.entry_id]["coordinator"]
coordinator = hass.data[PHYN_DOMAIN]["coordinator"]
entities = []
for device in coordinator.devices:
entities.extend([
71 changes: 71 additions & 0 deletions custom_components/phyn/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Config flow for phyn integration."""
from aiophyn import async_get_api
from aiophyn.errors import RequestError
from botocore.exceptions import ClientError
import voluptuous as vol

from homeassistant import config_entries, core, exceptions
@@ -16,6 +17,10 @@
vol.Required(CONF_PASSWORD): str,
vol.Required("Brand"): vol.In(BRANDS),
})
REAUTH_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str
})


async def validate_input(hass: core.HomeAssistant, data):
@@ -41,6 +46,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for phyn."""

VERSION = 1
MINOR_VERSION = 2

async def async_step_user(self, user_input=None):
"""Handle the initial step."""
@@ -51,13 +57,78 @@ async def async_step_user(self, user_input=None):
try:
info = await validate_input(self.hass, user_input)
return self.async_create_entry(title=info["title"], data=user_input)
except ClientError as error:
if error.response['Error']['Code'] == "NotAuthorizedException":
errors["base"] = "invalid_auth"
else:
errors["base"] = "cannot_connect"
except CannotConnect:
errors["base"] = "cannot_connect"

return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)

async def async_step_reauth(self, entry_data):
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(self, user_input=None):
errors = {}
if user_input is not None:
reauth_entry = self._get_reauth_entry()
user_input.update({"Brand": reauth_entry.data.get("Brand")})
try:
info = await validate_input(self.hass, user_input)
return self.async_update_reload_and_abort(
reauth_entry,
data_updates={
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD]
}
)
return self.async_create_entry(title=info["title"], data=user_input)
except ClientError as error:
if error.response['Error']['Code'] == "NotAuthorizedException":
errors["base"] = "invalid_auth"
else:
errors["base"] = "cannot_connect"
except CannotConnect:
errors["base"] = "cannot_connect"

return self.async_show_form(
step_id="reauth_confirm",
data_schema=REAUTH_SCHEMA,
errors=errors
)

async def async_step_reconfigure(self, user_input: dict[str, any] | None = None):
errors = {}
reconfigure_entry = self._get_reconfigure_entry()
LOGGER.debug("Reconfigure entry: %s" % reconfigure_entry)
if user_input is not None:
try:
info = await validate_input(self.hass, user_input)
return self.async_update_reload_and_abort(
reconfigure_entry, data_updates={
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
"Brand": user_input["Brand"]
}
)
return self.async_create_entry(title=info["title"], data=user_input)
except ClientError as error:
if error.response['Error']['Code'] == "NotAuthorizedException":
errors["base"] = "invalid_auth"
else:
errors["base"] = "cannot_connect"
except CannotConnect:
errors["base"] = "cannot_connect"

return self.async_show_form(
step_id="reconfigure",
data_schema=DATA_SCHEMA,
errors=errors
)

class CannotConnect(exceptions.HomeAssistantError):
"""Error to indicate we cannot connect."""
1 change: 1 addition & 0 deletions custom_components/phyn/const.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Constants for the phyn integration."""
import logging
from enum import StrEnum

LOGGER = logging.getLogger(__package__)

14 changes: 10 additions & 4 deletions custom_components/phyn/devices/base.py
Original file line number Diff line number Diff line change
@@ -2,6 +2,8 @@

from typing import Any
from ..const import LOGGER
import math
import time

class PhynDevice:
"""Generice Phyn Device"""
@@ -86,6 +88,9 @@ def rssi(self) -> float:
def serial_number(self) -> str:
"""Return the serial number for the device."""
return self._device_state["serial_number"]

async def async_setup(self):
pass

async def _update_firmware_information(self, *_) -> None:
self._firmware_info.update(
@@ -95,7 +100,8 @@ async def _update_firmware_information(self, *_) -> None:

async def _update_device_state(self, *_) -> None:
"""Update the device state from the API."""
self._device_state.update(await self._coordinator.api_client.device.get_state(
self._phyn_device_id
))
#LOGGER.debug("Phyn device state: %s", self._device_state)
if 'last_updated' not in self._device_state or self._device_state['last_updated'] <= (math.floor(time.time()) - 60):
self._device_state.update(await self._coordinator.api_client.device.get_state(
self._phyn_device_id
))
self._device_state['last_updated'] = math.floor(time.time())
Loading