From 85cb49db7bfb6025e73051f6202ce321a12a8adf Mon Sep 17 00:00:00 2001 From: Chris J <421501+cryptk@users.noreply.github.com> Date: Mon, 15 May 2023 13:56:27 -0500 Subject: [PATCH] Multiple devices to work with multiple BOWs --- .gitignore | 3 +++ custom_components/omnilogic_local/__init__.py | 27 ++++++++++++++----- custom_components/omnilogic_local/const.py | 7 +++++ .../omnilogic_local/coordinator.py | 17 +++++++----- custom_components/omnilogic_local/types.py | 10 ++++--- .../omnilogic_local/water_heater.py | 4 +-- 6 files changed, 50 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 68bc17f..6969789 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Allow us to use provided diagnostic data to reproduce issues +custom_components/omnilogic_local/test_diagnostic_data.py + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] diff --git a/custom_components/omnilogic_local/__init__.py b/custom_components/omnilogic_local/__init__.py index 5309a29..e54a96f 100644 --- a/custom_components/omnilogic_local/__init__.py +++ b/custom_components/omnilogic_local/__init__.py @@ -15,8 +15,10 @@ from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr -from .const import DOMAIN, KEY_COORDINATOR, UNIQUE_ID +from .const import DOMAIN, KEY_COORDINATOR, UNIQUE_ID, KEY_MSP_BACKYARD, KEY_MSP_BOW, BACKYARD_SYSTEM_ID from .coordinator import OmniLogicCoordinator +from .utils import get_entities_of_omni_type +import logging PLATFORMS: list[Platform] = [ Platform.BINARY_SENSOR, @@ -28,6 +30,7 @@ Platform.WATER_HEATER, ] +_LOGGER = logging.getLogger(__name__) async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up OmniLogic Local from a config entry.""" @@ -48,20 +51,32 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: await coordinator.async_config_entry_first_refresh() device_registry = dr.async_get(hass) + + #Create a device for the Omni Backyard + backyard = get_entities_of_omni_type(coordinator.data, KEY_MSP_BACKYARD)[BACKYARD_SYSTEM_ID] device_registry.async_get_or_create( config_entry_id=entry.entry_id, - identifiers={(DOMAIN, UNIQUE_ID)}, + identifiers={(KEY_MSP_BACKYARD, BACKYARD_SYSTEM_ID)}, manufacturer="Hayward", - # TODO: Figure out how to manage device naming, the API does not return a name - name="omnilogic", + suggested_area="Back Yard", + name=f"{entry.data[CONF_NAME]}_{backyard['metadata']['name']}", ) + # Create a device for each Body of Water + for system_id, bow in get_entities_of_omni_type(coordinator.data, KEY_MSP_BOW).items(): + device_registry.async_get_or_create( + config_entry_id=entry.entry_id, + identifiers={(KEY_MSP_BOW, system_id)}, + manufacturer="Hayward", + suggested_area="Back Yard", + # TODO: Figure out how to manage device naming, the API does not return a name + name=f"{entry.data[CONF_NAME]}_{bow['metadata']['name']}", + ) + # Store them for use later hass.data.setdefault(DOMAIN, {}) hass.data[DOMAIN][entry.entry_id] = { KEY_COORDINATOR: coordinator, - # KEY_DEVICE_REGISTRY: device_registry - # KEY_OMNI_API: omni_api, } await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) diff --git a/custom_components/omnilogic_local/const.py b/custom_components/omnilogic_local/const.py index 26ceeef..12b0ace 100644 --- a/custom_components/omnilogic_local/const.py +++ b/custom_components/omnilogic_local/const.py @@ -19,6 +19,13 @@ KEY_TELEMETRY_BOW = "BodyOfWater" KEY_TELEMETRY_SYSTEM_ID = "@systemId" +OMNI_BOW_TYPE_POOL = "BOW_POOL" +OMNI_BOW_TYPE_SPA = "BOW_SPA" +OMNI_BOW_TYPES = [ + OMNI_BOW_TYPE_POOL, + OMNI_BOW_TYPE_SPA +] + OMNI_DEVICE_TYPES_FILTER = [ "Filter", ] diff --git a/custom_components/omnilogic_local/coordinator.py b/custom_components/omnilogic_local/coordinator.py index 57136b5..9d697cc 100644 --- a/custom_components/omnilogic_local/coordinator.py +++ b/custom_components/omnilogic_local/coordinator.py @@ -16,8 +16,13 @@ KEY_MSP_SYSTEM_ID, OMNI_DEVICE_TYPES, OMNI_TO_HASS_TYPES, + OMNI_BOW_TYPES, ) from .utils import get_telemetry_by_systemid, one_or_many +import json + +# Import diagnostic data to reproduce issues +from .test_diagnostic_data import TEST_DIAGNOSTIC_DATA _LOGGER = logging.getLogger(__name__) @@ -81,7 +86,7 @@ def build_entity_index(data: dict[str, str]) -> dict[int, dict[str, str]]: for item in one_or_many(tier): bow_id = ( - int(item[KEY_MSP_SYSTEM_ID]) if item.get("Type") == "BOW_POOL" else None + int(item[KEY_MSP_SYSTEM_ID]) if item.get("Type") in OMNI_BOW_TYPES else None ) for omni_entity_type, entity_data in item.items(): if omni_entity_type not in OMNI_DEVICE_TYPES: @@ -125,11 +130,6 @@ async def _async_update_data(self): # Note: asyncio.TimeoutError and aiohttp.ClientError are already # handled by the data update coordinator. async with async_timeout.timeout(10): - # Grab active context variables to limit data required to be fetched from API - # Note: using context is not required if there is no need or ability to limit - # data retrieved from API. - # listening_idx = set(self.async_contexts()) - # Initially we only pulled the msp_config at integration startup as it rarely changes # Then we learned that heater set points (which can change often enough) are stored # within the MSP Config, not the telemetry, so now we pull the msp_config on every update @@ -143,6 +143,11 @@ async def _async_update_data(self): await self.omni_api.async_get_telemetry() ) + # The below is used if we have a test_diagnostic_data.py populated with a diagnostic data file to reproduce an issue + # test_data = json.loads(TEST_DIAGNOSTIC_DATA) + # self.msp_config = test_data['data']['msp_config'] + # self.telemetry = test_data['data']['telemetry'] + omnilogic_data = self.msp_config | self.telemetry entity_index = build_entity_index(omnilogic_data) diff --git a/custom_components/omnilogic_local/types.py b/custom_components/omnilogic_local/types.py index 9a7125f..432184e 100644 --- a/custom_components/omnilogic_local/types.py +++ b/custom_components/omnilogic_local/types.py @@ -7,7 +7,7 @@ from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import BACKYARD_SYSTEM_ID, DOMAIN, MANUFACTURER, UNIQUE_ID +from .const import BACKYARD_SYSTEM_ID, DOMAIN, MANUFACTURER, UNIQUE_ID, KEY_MSP_BOW, KEY_MSP_BACKYARD if TYPE_CHECKING: from .coordinator import OmniLogicCoordinator @@ -90,10 +90,14 @@ def available(self) -> bool: @property def device_info(self) -> DeviceInfo: """Return the device info.""" + # If we have a BOW ID, then we associate with that BOWs device, if not, we associate with the Backyard + if self.bow_id is not None: + identifiers = {(KEY_MSP_BOW, self.bow_id)} + else: + identifiers = {(KEY_MSP_BACKYARD, BACKYARD_SYSTEM_ID)} return DeviceInfo( - identifiers={(DOMAIN, UNIQUE_ID)}, + identifiers=identifiers, manufacturer=MANUFACTURER, - # model=self.model, ) @property diff --git a/custom_components/omnilogic_local/water_heater.py b/custom_components/omnilogic_local/water_heater.py index b760c41..e27c4ce 100644 --- a/custom_components/omnilogic_local/water_heater.py +++ b/custom_components/omnilogic_local/water_heater.py @@ -153,9 +153,7 @@ def extra_state_attributes(self) -> Mapping[str, Any] | None: f"{prefix}_enabled": self.get_config(system_id)["Enabled"], f"{prefix}_system_id": system_id, f"{prefix}_bow_id": heater_equipment["metadata"]["bow_id"], - f"{prefix}_supports_cooling": self.get_config(system_id)[ - "SupportsCooling" - ], + f"{prefix}_supports_cooling": self.get_config(system_id).get("SupportsCooling", "no"), f"{prefix}_state": STATE_ON if self.get_telemetry(system_id)["@heaterState"] == "1" else STATE_OFF,