Skip to content

Commit c48b302

Browse files
committed
Significant refactoring
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.
1 parent 7793350 commit c48b302

File tree

11 files changed

+691
-281
lines changed

11 files changed

+691
-281
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repos:
1010
- id: black
1111
args:
1212
- --safe
13-
- --quiet
13+
# - --quiet
1414
- repo: https://github.com/codespell-project/codespell
1515
rev: v1.17.1
1616
hooks:

custom_components/intellicenter/__init__.py

Lines changed: 79 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@
99
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
1010
from homeassistant.components.water_heater import DOMAIN as WATER_HEATER_DOMAIN
1111
from homeassistant.config_entries import ConfigEntry
12-
from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP
12+
from homeassistant.const import (
13+
CONF_HOST,
14+
EVENT_HOMEASSISTANT_STOP,
15+
TEMP_CELSIUS,
16+
TEMP_FAHRENHEIT,
17+
)
1318
from homeassistant.core import HomeAssistant, callback
1419
from homeassistant.exceptions import ConfigEntryNotReady
1520
from homeassistant.helpers import dispatcher
1621
from homeassistant.helpers.entity import Entity
1722
from homeassistant.helpers.typing import ConfigType
1823

1924
from .const import DOMAIN
20-
from .pyintellicenter import ConnectionHandler, ModelController, PoolModel
25+
from .pyintellicenter import ConnectionHandler, ModelController, PoolModel, PoolObject
2126

2227
_LOGGER = logging.getLogger(__name__)
2328

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

42-
# we are only interested in a subset of all objects
43-
def filterFunc(object):
44-
"""Return true for the objects we care about."""
45-
return object.status and object.objtype in [
46-
"BODY",
47-
"SENSE",
48-
"PUMP",
49-
"HEATER",
50-
"CIRCUIT",
51-
]
47+
# we don't need some of the system objects
48+
def ignoreFunc(object):
49+
"""Return False for the objects we want to ignore."""
50+
51+
return (
52+
object.objtype
53+
in [
54+
"PANEL",
55+
"MODULE",
56+
"PERMIT",
57+
"SYSTIM",
58+
]
59+
or object.subtype in ["LEGACY"]
60+
)
5261

53-
model = PoolModel(filterFunc)
62+
attributes_map = {
63+
"BODY": {"SNAME", "HEATER", "HTMODE", "LOTMP", "LSTTMP", "STATUS"},
64+
"CIRCUIT": {"SNAME", "STATUS", "USE", "SUBTYPE", "FEATR"},
65+
"CIRCGRP": {"CIRCUIT"},
66+
"HEATER": {"SNAME", "BODY"},
67+
"PUMP": {"SNAME", "STATUS", "PWR", "RPM", "GPM"},
68+
"SENSE": {"SNAME", "SOURCE"},
69+
}
70+
model = PoolModel(attributes_map)
5471

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

5774
class Handler(ConnectionHandler):
5875

5976
UPDATE_SIGNAL = DOMAIN + "_UPDATE_" + entry.entry_id
77+
CONNECTION_SIGNAL = DOMAIN + "_CONNECTION_" + entry.entry_id
6078

6179
def started(self, controller):
6280

@@ -74,32 +92,29 @@ async def setup_platforms():
7492
]
7593
)
7694

95+
# dispatcher.async_dispatcher_send(hass, self.CONNECTION_SIGNAL, True)
96+
7797
hass.async_create_task(setup_platforms())
7898

7999
@callback
80100
def reconnected(self, controller):
81101
"""Handle reconnection from the Pentair system."""
82102
_LOGGER.info(f"reconnected to system: '{controller.systemInfo.propName}'")
83-
dispatcher.async_dispatcher_send(
84-
hass, self.UPDATE_SIGNAL, controller.model.objectList
85-
)
103+
dispatcher.async_dispatcher_send(hass, self.CONNECTION_SIGNAL, True)
86104

87105
@callback
88106
def disconnected(self, controller, exc):
89107
"""Handle updates from the Pentair system."""
90108
_LOGGER.info(
91109
f"disconnected from system: '{controller.systemInfo.propName}'"
92110
)
93-
dispatcher.async_dispatcher_send(
94-
hass, DOMAIN + "_DISCONNECT_" + entry.entry_id
95-
)
111+
dispatcher.async_dispatcher_send(hass, self.CONNECTION_SIGNAL, False)
96112

97113
@callback
98-
def updated(self, controller, changes):
114+
def updated(self, controller, updates: Dict[str, PoolObject]):
99115
"""Handle updates from the Pentair system."""
100-
for object in changes:
101-
_LOGGER.debug(f"received update for {object}")
102-
dispatcher.async_dispatcher_send(hass, self.UPDATE_SIGNAL, changes)
116+
_LOGGER.debug(f"received update for {len(updates)} pool objects")
117+
dispatcher.async_dispatcher_send(hass, self.UPDATE_SIGNAL, updates)
103118

104119
try:
105120

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

158-
def __init__(self, entry: ConfigEntry, controller, poolObject):
173+
def __init__(
174+
self,
175+
entry: ConfigEntry,
176+
controller: ModelController,
177+
poolObject: PoolObject,
178+
attribute_key="STATUS",
179+
name_suffix="",
180+
):
159181
"""Initialize a Pool entity."""
160182
self._entry_id = entry.entry_id
161183
self._controller = controller
162184
self._poolObject = poolObject
163185
self._available = True
164186
self._extraStateAttributes = []
187+
self._name_suffix = name_suffix
188+
self._attribute_key = attribute_key
165189

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

@@ -176,8 +200,8 @@ async def async_added_to_hass(self):
176200
self.async_on_remove(
177201
dispatcher.async_dispatcher_connect(
178202
self.hass,
179-
DOMAIN + "_DISCONNECT_" + self._entry_id,
180-
self._disconnect_callback,
203+
DOMAIN + "_CONNECTION_" + self._entry_id,
204+
self._connection_callback,
181205
)
182206
)
183207

@@ -193,12 +217,18 @@ def available(self):
193217
@property
194218
def name(self):
195219
"""Return the name of the entity."""
196-
return self._poolObject.sname
220+
name = self._poolObject.sname
221+
if self._name_suffix:
222+
name += " " + self._name_suffix
223+
return name
197224

198225
@property
199226
def unique_id(self):
200227
"""Return a unique ID."""
201-
return self._entry_id + self._poolObject.objnam
228+
my_id = self._entry_id + self._poolObject.objnam
229+
if self._attribute_key != "STATUS":
230+
my_id += self._attribute_key
231+
return my_id
202232

203233
@property
204234
def should_poll(self):
@@ -250,15 +280,29 @@ def requestChanges(self, changes: dict) -> None:
250280
)
251281

252282
@callback
253-
def _update_callback(self, changes):
283+
def _update_callback(self, updates: Dict[str, Dict[str, str]]):
254284
"""Update the entity if its underlying pool object has changed."""
255-
for object in changes:
256-
if object.objnam == self._poolObject.objnam:
257-
self._available = True
258-
self.async_write_ha_state()
285+
286+
if self._attribute_key in updates.get(self._poolObject.objnam, {}):
287+
self._available = True
288+
my_updates = updates.get(self._poolObject.objnam)
289+
_LOGGER.debug(f"updating {self} from {my_updates}")
290+
self.async_write_ha_state()
259291

260292
@callback
261-
def _disconnect_callback(self):
293+
def _connection_callback(self, is_connected):
262294
"""Mark the entity as unavailable after being disconnected from the server."""
263-
self._available = False
295+
if is_connected:
296+
self._poolObject = self._controller.model[self._poolObject.objnam]
297+
if not self._poolObject:
298+
# this is for the rare case where the object the entity is mapped to
299+
# had been removed from the Pentair system while we were disconnected
300+
return
301+
self._available = is_connected
264302
self.async_write_ha_state()
303+
304+
def pentairTemperatureSettings(self):
305+
"""Return the temperature units from the Pentair system."""
306+
return (
307+
TEMP_CELSIUS if self._controller.systemInfo.usesMetric else TEMP_FAHRENHEIT
308+
)

custom_components/intellicenter/binary_sensor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from . import PoolEntity
1010
from .const import DOMAIN
11+
from .pyintellicenter import ModelController, PoolObject
1112

1213
_LOGGER = logging.getLogger(__name__)
1314

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

20-
controller = hass.data[DOMAIN][entry.entry_id].controller
21+
controller: ModelController = hass.data[DOMAIN][entry.entry_id].controller
2122

2223
sensors = []
2324

25+
object: PoolObject
2426
for object in controller.model.objectList:
2527
if (
2628
object.objtype == "CIRCUIT" and object.subtype == "FRZ"

custom_components/intellicenter/light.py

Lines changed: 64 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Pentair Intellicenter lights."""
22

3+
from functools import reduce
34
import logging
4-
from typing import Any
5+
from typing import Any, Dict
56

67
from homeassistant.components.light import ATTR_EFFECT, SUPPORT_EFFECT, LightEntity
78
from homeassistant.config_entries import ConfigEntry
9+
from homeassistant.core import callback
810
from homeassistant.helpers.typing import HomeAssistantType
911

1012
from . import PoolEntity
@@ -13,44 +15,72 @@
1315

1416
_LOGGER = logging.getLogger(__name__)
1517

18+
LIGHTS_EFFECTS = {
19+
"PARTY": "Party Mode",
20+
"CARIB": "Caribbean",
21+
"SSET": "Sunset",
22+
"ROMAN": "Romance",
23+
"AMERCA": "American",
24+
"ROYAL": "Royal",
25+
"WHITER": "White",
26+
"REDR": "Red",
27+
"BLUER": "Blue",
28+
"GREENR": "Green",
29+
"MAGNTAR": "Magenta",
30+
}
31+
1632

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

22-
controller = hass.data[DOMAIN][entry.entry_id].controller
38+
controller: ModelController = hass.data[DOMAIN][entry.entry_id].controller
2339

2440
lights = []
2541

42+
object: PoolObject
2643
for object in controller.model.objectList:
2744
if object.isALight:
28-
lights.append(PoolLight(entry, controller, object))
29-
async_add_entities(lights)
30-
45+
lights.append(
46+
PoolLight(
47+
entry,
48+
controller,
49+
object,
50+
LIGHTS_EFFECTS if object.supportColorEffects else None,
51+
)
52+
)
53+
elif object.isALightShow:
54+
55+
supportColorEffects = reduce(
56+
lambda x, y: x and y,
57+
map(
58+
lambda obj: controller.model[obj["CIRCUIT"]].supportColorEffects,
59+
controller.model.getChildren(object),
60+
),
61+
True,
62+
)
63+
lights.append(
64+
PoolLight(
65+
entry,
66+
controller,
67+
object,
68+
LIGHTS_EFFECTS if supportColorEffects else None,
69+
)
70+
)
3171

32-
LIGHTS_EFFECTS_BY_TYPE = {
33-
"INTELLI": {
34-
"PARTY": "Party Mode",
35-
"CARIB": "Caribbean",
36-
"SSET": "Sunset",
37-
"ROMAN": "Romance",
38-
"AMERCA": "American",
39-
"ROYAL": "Royal",
40-
"WHITER": "White",
41-
"REDR": "Red",
42-
"BLUER": "Blue",
43-
"GREENR": "Green",
44-
"MAGNTAR": "Magenta",
45-
}
46-
}
72+
async_add_entities(lights)
4773

4874

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

5278
def __init__(
53-
self, entry: ConfigEntry, controller: ModelController, poolObject: PoolObject
79+
self,
80+
entry: ConfigEntry,
81+
controller: ModelController,
82+
poolObject: PoolObject,
83+
colorEffects: dict = None,
5484
):
5585
"""Initialize."""
5686
super().__init__(entry, controller, poolObject)
@@ -59,8 +89,10 @@ def __init__(
5989

6090
self._features = 0
6191

62-
self._lightEffects = LIGHTS_EFFECTS_BY_TYPE.get(poolObject.subtype, {})
63-
self._reversedLightEffects = dict(map(reversed, self._lightEffects.items()))
92+
self._lightEffects = colorEffects
93+
self._reversedLightEffects = (
94+
dict(map(reversed, colorEffects.items())) if colorEffects else None
95+
)
6496

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

103135
self.requestChanges(changes)
136+
137+
@callback
138+
def _update_callback(self, updates: Dict[str, PoolObject]):
139+
"""Update the entity if its underlying pool object has changed."""
140+
141+
if self._poolObject.objnam in updates:
142+
self._available = True
143+
_LOGGER.debug(f"updating {self} from {self._poolObject}")
144+
self.async_write_ha_state()

custom_components/intellicenter/pyintellicenter/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@
77
ModelController,
88
SystemInfo,
99
)
10-
from .model import ALL_KNOWN_ATTRIBUTES, PoolModel, PoolObject
10+
from .model import PoolModel, PoolObject
1111

1212
__all__ = [
1313
BaseController,
1414
CommandError,
1515
ConnectionHandler,
1616
ModelController,
1717
SystemInfo,
18-
ALL_KNOWN_ATTRIBUTES,
1918
PoolModel,
2019
PoolObject,
2120
]

0 commit comments

Comments
 (0)