Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart watering program fix #101

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions custom_components/bhyve/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
ConfigEntryNotReady,
HomeAssistantError,
)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import config_validation as cv, entity_registry
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.dispatcher import (
async_dispatcher_connect,
Expand All @@ -33,7 +33,7 @@
SIGNAL_UPDATE_DEVICE,
SIGNAL_UPDATE_PROGRAM,
)
from .util import filter_configured_devices
from .util import filter_configured_devices, constant_program_id
from .pybhyve import Client
from .pybhyve.errors import AuthenticationError, BHyveError

Expand Down Expand Up @@ -82,6 +82,11 @@ async def async_update_callback(data):
if event == EVENT_PROGRAM_CHANGED:
device_id = data.get("program", {}).get("device_id")
program_id = data.get("program", {}).get("id")
# Use a constant id if Smart program.
is_smart_program = bool(
data.get("program", {}).get("is_smart_program", False)
)
program_id = constant_program_id(device_id, program_id, is_smart_program)
else:
device_id = data.get("device_id")

Expand Down Expand Up @@ -142,6 +147,32 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return unload_ok


async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating Bhyve from version %s", entry.version)
version = entry.version
device_id = entry.options["devices"][0] # Use ID of first device

# Migrate Smart Watering program switch to new constant Unique ID
if version == 1:
registry = entity_registry.async_get(hass)
for entity_id, e_entry in registry.entities.items():
if e_entry.config_entry_id == entry.entry_id:
new_unique_id = f"bhyve:{device_id}:program:smart_program"
if entity_id.endswith(
"_smart_watering_program"
): # Only migrate the first switch
registry.async_update_entity(entity_id, new_unique_id=new_unique_id)
entry.version = 2
_LOGGER.info(
"Bhyve unique identifier for entity: %s has been updated",
entity_id,
)

_LOGGER.info("Migration to version %s successful", entry.version)
return True


class BHyveEntity(Entity):
"""Define a base BHyve entity."""

Expand Down
2 changes: 1 addition & 1 deletion custom_components/bhyve/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for BHyve."""

VERSION = 1
VERSION = 2

def __init__(self):
"""Initialize the config flow."""
Expand Down
22 changes: 17 additions & 5 deletions custom_components/bhyve/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
SIGNAL_UPDATE_PROGRAM,
)
from .pybhyve.errors import BHyveError
from .util import filter_configured_devices, orbit_time_to_local_time
from .util import (
filter_configured_devices,
orbit_time_to_local_time,
constant_program_id,
)

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -232,14 +236,15 @@ def __init__(self, hass, bhyve, device, program, icon):
self._device_id = program.get("device_id")
self._program_id = program.get("id")
self._available = True
self._is_smart_program = bool(self._program.get("is_smart_program", False))

@property
def extra_state_attributes(self):
"""Return the device state attributes."""

attrs = {
"device_id": self._device_id,
"is_smart_program": self._program.get("is_smart_program", False),
"is_smart_program": self._is_smart_program,
"frequency": self._program.get("frequency"),
"start_times": self._program.get("start_times"),
"budget": self._program.get("budget"),
Expand All @@ -256,8 +261,8 @@ def is_on(self):

@property
def unique_id(self):
"""Return the unique id for the switch program."""
return f"bhyve:program:{self._program_id}"
"""Return the unique id, unchanging string that represents this switch program."""
return f"bhyve:{self._device_id}:program:{'smart_program' if self._is_smart_program else self._program_id}"

@property
def entity_category(self):
Expand Down Expand Up @@ -290,8 +295,13 @@ def update(device_id, data):
self._ws_unprocessed_events.append(data)
self.async_schedule_update_ha_state(True)

# Use a constant id so that it is updated on change.
program_id = constant_program_id(
self._device_id, self._program_id, self._is_smart_program
)

self._async_unsub_dispatcher_connect = async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_PROGRAM.format(self._program_id), update
self.hass, SIGNAL_UPDATE_PROGRAM.format(program_id), update
)

async def async_will_remove_from_hass(self):
Expand All @@ -314,6 +324,8 @@ def _on_ws_data(self, data):
program = data.get("program")
if program is not None:
self._program = program
# Update Smart Watering program_id to match id if changed on irrigation system.
self._program_id = program.get("id")

def _should_handle_event(self, event_name, data):
return event_name in [EVENT_PROGRAM_CHANGED]
Expand Down
11 changes: 11 additions & 0 deletions custom_components/bhyve/util.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import hashlib
from homeassistant.config_entries import ConfigEntry
from homeassistant.util import dt

Expand All @@ -14,3 +15,13 @@ def orbit_time_to_local_time(timestamp: str):
def filter_configured_devices(entry: ConfigEntry, all_devices):
"""Filter the device list to those that are enabled in options."""
return [d for d in all_devices if str(d["id"]) in entry.options[CONF_DEVICES]]


def constant_program_id(device_id, program_id, is_smart_program: bool = False):
"""For devices with multiple zones, Smart program id changes depending on the zone/s that are included.
Generate a constant id so that it is updated on change."""
if is_smart_program:
program_id = hashlib.md5(
"{}:smart_program".format(device_id).encode("utf-8")
).hexdigest()
return program_id