Skip to content

Commit

Permalink
Significant refactoring
Browse files Browse the repository at this point in the history
Changed the boot sequence of the ModelController.
Added ability to fine tune the objects/attributes of interest.
Added support for Light effects on Light Groups.
  • Loading branch information
jlvaillant committed Dec 28, 2020
1 parent 7793350 commit c48b302
Show file tree
Hide file tree
Showing 11 changed files with 691 additions and 281 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repos:
- id: black
args:
- --safe
- --quiet
# - --quiet
- repo: https://github.com/codespell-project/codespell
rev: v1.17.1
hooks:
Expand Down
114 changes: 79 additions & 35 deletions custom_components/intellicenter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
from homeassistant.const import (
CONF_HOST,
EVENT_HOMEASSISTANT_STOP,
TEMP_CELSIUS,
TEMP_FAHRENHEIT,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import dispatcher
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.typing import ConfigType

from .const import DOMAIN
from .pyintellicenter import ConnectionHandler, ModelController, PoolModel
from .pyintellicenter import ConnectionHandler, ModelController, PoolModel, PoolObject

_LOGGER = logging.getLogger(__name__)

Expand All @@ -39,24 +44,37 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up IntelliCenter integration from a config entry."""

# we are only interested in a subset of all objects
def filterFunc(object):
"""Return true for the objects we care about."""
return object.status and object.objtype in [
"BODY",
"SENSE",
"PUMP",
"HEATER",
"CIRCUIT",
]
# we don't need some of the system objects
def ignoreFunc(object):
"""Return False for the objects we want to ignore."""

return (
object.objtype
in [
"PANEL",
"MODULE",
"PERMIT",
"SYSTIM",
]
or object.subtype in ["LEGACY"]
)

model = PoolModel(filterFunc)
attributes_map = {
"BODY": {"SNAME", "HEATER", "HTMODE", "LOTMP", "LSTTMP", "STATUS"},
"CIRCUIT": {"SNAME", "STATUS", "USE", "SUBTYPE", "FEATR"},
"CIRCGRP": {"CIRCUIT"},
"HEATER": {"SNAME", "BODY"},
"PUMP": {"SNAME", "STATUS", "PWR", "RPM", "GPM"},
"SENSE": {"SNAME", "SOURCE"},
}
model = PoolModel(attributes_map)

controller = ModelController(entry.data[CONF_HOST], model, loop=hass.loop)

class Handler(ConnectionHandler):

UPDATE_SIGNAL = DOMAIN + "_UPDATE_" + entry.entry_id
CONNECTION_SIGNAL = DOMAIN + "_CONNECTION_" + entry.entry_id

def started(self, controller):

Expand All @@ -74,32 +92,29 @@ async def setup_platforms():
]
)

# dispatcher.async_dispatcher_send(hass, self.CONNECTION_SIGNAL, True)

hass.async_create_task(setup_platforms())

@callback
def reconnected(self, controller):
"""Handle reconnection from the Pentair system."""
_LOGGER.info(f"reconnected to system: '{controller.systemInfo.propName}'")
dispatcher.async_dispatcher_send(
hass, self.UPDATE_SIGNAL, controller.model.objectList
)
dispatcher.async_dispatcher_send(hass, self.CONNECTION_SIGNAL, True)

@callback
def disconnected(self, controller, exc):
"""Handle updates from the Pentair system."""
_LOGGER.info(
f"disconnected from system: '{controller.systemInfo.propName}'"
)
dispatcher.async_dispatcher_send(
hass, DOMAIN + "_DISCONNECT_" + entry.entry_id
)
dispatcher.async_dispatcher_send(hass, self.CONNECTION_SIGNAL, False)

@callback
def updated(self, controller, changes):
def updated(self, controller, updates: Dict[str, PoolObject]):
"""Handle updates from the Pentair system."""
for object in changes:
_LOGGER.debug(f"received update for {object}")
dispatcher.async_dispatcher_send(hass, self.UPDATE_SIGNAL, changes)
_LOGGER.debug(f"received update for {len(updates)} pool objects")
dispatcher.async_dispatcher_send(hass, self.UPDATE_SIGNAL, updates)

try:

Expand Down Expand Up @@ -155,13 +170,22 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
class PoolEntity(Entity):
"""Representation of an Pool entity linked to an pool object."""

def __init__(self, entry: ConfigEntry, controller, poolObject):
def __init__(
self,
entry: ConfigEntry,
controller: ModelController,
poolObject: PoolObject,
attribute_key="STATUS",
name_suffix="",
):
"""Initialize a Pool entity."""
self._entry_id = entry.entry_id
self._controller = controller
self._poolObject = poolObject
self._available = True
self._extraStateAttributes = []
self._name_suffix = name_suffix
self._attribute_key = attribute_key

_LOGGER.debug(f"mapping {poolObject}")

Expand All @@ -176,8 +200,8 @@ async def async_added_to_hass(self):
self.async_on_remove(
dispatcher.async_dispatcher_connect(
self.hass,
DOMAIN + "_DISCONNECT_" + self._entry_id,
self._disconnect_callback,
DOMAIN + "_CONNECTION_" + self._entry_id,
self._connection_callback,
)
)

Expand All @@ -193,12 +217,18 @@ def available(self):
@property
def name(self):
"""Return the name of the entity."""
return self._poolObject.sname
name = self._poolObject.sname
if self._name_suffix:
name += " " + self._name_suffix
return name

@property
def unique_id(self):
"""Return a unique ID."""
return self._entry_id + self._poolObject.objnam
my_id = self._entry_id + self._poolObject.objnam
if self._attribute_key != "STATUS":
my_id += self._attribute_key
return my_id

@property
def should_poll(self):
Expand Down Expand Up @@ -250,15 +280,29 @@ def requestChanges(self, changes: dict) -> None:
)

@callback
def _update_callback(self, changes):
def _update_callback(self, updates: Dict[str, Dict[str, str]]):
"""Update the entity if its underlying pool object has changed."""
for object in changes:
if object.objnam == self._poolObject.objnam:
self._available = True
self.async_write_ha_state()

if self._attribute_key in updates.get(self._poolObject.objnam, {}):
self._available = True
my_updates = updates.get(self._poolObject.objnam)
_LOGGER.debug(f"updating {self} from {my_updates}")
self.async_write_ha_state()

@callback
def _disconnect_callback(self):
def _connection_callback(self, is_connected):
"""Mark the entity as unavailable after being disconnected from the server."""
self._available = False
if is_connected:
self._poolObject = self._controller.model[self._poolObject.objnam]
if not self._poolObject:
# this is for the rare case where the object the entity is mapped to
# had been removed from the Pentair system while we were disconnected
return
self._available = is_connected
self.async_write_ha_state()

def pentairTemperatureSettings(self):
"""Return the temperature units from the Pentair system."""
return (
TEMP_CELSIUS if self._controller.systemInfo.usesMetric else TEMP_FAHRENHEIT
)
4 changes: 3 additions & 1 deletion custom_components/intellicenter/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from . import PoolEntity
from .const import DOMAIN
from .pyintellicenter import ModelController, PoolObject

_LOGGER = logging.getLogger(__name__)

Expand All @@ -17,10 +18,11 @@ async def async_setup_entry(
):
"""Load pool sensors based on a config entry."""

controller = hass.data[DOMAIN][entry.entry_id].controller
controller: ModelController = hass.data[DOMAIN][entry.entry_id].controller

sensors = []

object: PoolObject
for object in controller.model.objectList:
if (
object.objtype == "CIRCUIT" and object.subtype == "FRZ"
Expand Down
87 changes: 64 additions & 23 deletions custom_components/intellicenter/light.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Pentair Intellicenter lights."""

from functools import reduce
import logging
from typing import Any
from typing import Any, Dict

from homeassistant.components.light import ATTR_EFFECT, SUPPORT_EFFECT, LightEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import callback
from homeassistant.helpers.typing import HomeAssistantType

from . import PoolEntity
Expand All @@ -13,44 +15,72 @@

_LOGGER = logging.getLogger(__name__)

LIGHTS_EFFECTS = {
"PARTY": "Party Mode",
"CARIB": "Caribbean",
"SSET": "Sunset",
"ROMAN": "Romance",
"AMERCA": "American",
"ROYAL": "Royal",
"WHITER": "White",
"REDR": "Red",
"BLUER": "Blue",
"GREENR": "Green",
"MAGNTAR": "Magenta",
}


async def async_setup_entry(
hass: HomeAssistantType, entry: ConfigEntry, async_add_entities
):
"""Load pool lights based on a config entry."""

controller = hass.data[DOMAIN][entry.entry_id].controller
controller: ModelController = hass.data[DOMAIN][entry.entry_id].controller

lights = []

object: PoolObject
for object in controller.model.objectList:
if object.isALight:
lights.append(PoolLight(entry, controller, object))
async_add_entities(lights)

lights.append(
PoolLight(
entry,
controller,
object,
LIGHTS_EFFECTS if object.supportColorEffects else None,
)
)
elif object.isALightShow:

supportColorEffects = reduce(
lambda x, y: x and y,
map(
lambda obj: controller.model[obj["CIRCUIT"]].supportColorEffects,
controller.model.getChildren(object),
),
True,
)
lights.append(
PoolLight(
entry,
controller,
object,
LIGHTS_EFFECTS if supportColorEffects else None,
)
)

LIGHTS_EFFECTS_BY_TYPE = {
"INTELLI": {
"PARTY": "Party Mode",
"CARIB": "Caribbean",
"SSET": "Sunset",
"ROMAN": "Romance",
"AMERCA": "American",
"ROYAL": "Royal",
"WHITER": "White",
"REDR": "Red",
"BLUER": "Blue",
"GREENR": "Green",
"MAGNTAR": "Magenta",
}
}
async_add_entities(lights)


class PoolLight(PoolEntity, LightEntity):
"""Representation of an Pentair light."""

def __init__(
self, entry: ConfigEntry, controller: ModelController, poolObject: PoolObject
self,
entry: ConfigEntry,
controller: ModelController,
poolObject: PoolObject,
colorEffects: dict = None,
):
"""Initialize."""
super().__init__(entry, controller, poolObject)
Expand All @@ -59,8 +89,10 @@ def __init__(

self._features = 0

self._lightEffects = LIGHTS_EFFECTS_BY_TYPE.get(poolObject.subtype, {})
self._reversedLightEffects = dict(map(reversed, self._lightEffects.items()))
self._lightEffects = colorEffects
self._reversedLightEffects = (
dict(map(reversed, colorEffects.items())) if colorEffects else None
)

if self._lightEffects:
self._features |= SUPPORT_EFFECT
Expand Down Expand Up @@ -101,3 +133,12 @@ def turn_on(self, **kwargs: Any) -> None:
changes["ACT"] = new_use

self.requestChanges(changes)

@callback
def _update_callback(self, updates: Dict[str, PoolObject]):
"""Update the entity if its underlying pool object has changed."""

if self._poolObject.objnam in updates:
self._available = True
_LOGGER.debug(f"updating {self} from {self._poolObject}")
self.async_write_ha_state()
3 changes: 1 addition & 2 deletions custom_components/intellicenter/pyintellicenter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
ModelController,
SystemInfo,
)
from .model import ALL_KNOWN_ATTRIBUTES, PoolModel, PoolObject
from .model import PoolModel, PoolObject

__all__ = [
BaseController,
CommandError,
ConnectionHandler,
ModelController,
SystemInfo,
ALL_KNOWN_ATTRIBUTES,
PoolModel,
PoolObject,
]
Loading

0 comments on commit c48b302

Please sign in to comment.