Skip to content

Commit 35b3b20

Browse files
committed
Fixes and features
1 parent 019ebdc commit 35b3b20

File tree

10 files changed

+307
-137
lines changed

10 files changed

+307
-137
lines changed

POLYGLOT_CONFIG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
## iAquaLink NodeServer Configuration
22
####Advanced Configuration:
3-
- shortPoll - polling interval for iAquaLink cloud service in "active" polling mode (15 seconds)
4-
- longPoll - polling interval for iAquaLink cloud service when not in "active" polling mode (60 seconds)
3+
- key: shortPoll, value: polling interval for iAquaLink cloud service in "active" polling mode (defaults to 15 seconds).
4+
- key: longPoll, value: polling interval for iAquaLink cloud service when not in "active" polling mode (defaults to 180 seconds).
55

66
####Custom Configuration Parameters:
7-
- username - username (email address) for logging into the iAquaLink service
8-
- password - password for logging into the iAquaLink service.
7+
- key: username, value: username (email address) for logging into the iAquaLink service (required).
8+
- key: password, value: password for logging into the iAquaLink service (required).
9+
10+
Once the "iAquaLink Nodeserver" node appears in The ISY Administrative Console and shows as Online, press the "Discover Devices" button to load the systems and devices configured in your iAquaLink profile.

README.MD

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ A NodeServer for Polyglot v2 that interfaces to the iAquaLink™ cloud service t
1010

1111
Advanced Configuration:
1212
```
13-
"shortPoll" = polling interval for iAquaLink cloud service in "active" polling mode - defaults to 15 seconds (better to go no more frequent than this).
14-
"longPoll" = polling interval for iAquaLink cloud service when not in "active" polling mode - defaults to 60 seconds.
13+
key: shortPoll, value: polling interval for iAquaLink cloud service in "active" polling mode (defaults to 15 seconds - better to not go more frequent than this).
14+
key: longPoll, value: polling interval for iAquaLink cloud service when not in "active" polling mode (defaults to 180 seconds).
1515
```
1616
Custom Configuration Parameters:
1717
```
18-
"username" = username (email address) for logging into the iAquaLink service.
19-
"password" = password for logging into the iAquaLink service.
18+
key: username, value: username (email address) for logging into the iAquaLink service (required).
19+
key: password, value: password for logging into the iAquaLink service (required).
20+
key: sessionTTL, value: number of seconds that the session ID is refreshed in order to avoid timeout (optional - defaults to 43200 (12 hours))
2021
```
2122
5. Once the "iAquaLink NodeServer" node appears in ISY994i Adminstative Console, click "Discover Devices" to load nodes for each of the system devices and aux relays on the pool controller(s) in your profile. THIS PROCESS MAY TAKE SEVERAL SECONDS depending on the number of systems you have and the activity on the iAqauLink service, so please be patient and wait 30 seconds or more before retrying. Also, please check the Polyglot Dashboard for messages regarding Discover Devices failure conditions.
2223

2324
###Notes:
2425

2526
1. The nodeserver relies on polling of the iAquaLink service and toggling of device states. Because of this, the nodeserver must first update the state before peforming any On, Off, etc. commands. Please be patient and provide time (up to shortPoll seconds) for the state change to be reflected before retrying your command.
27+
2. If you change the setup on your AquaLink (temperature unit, type of devices assigned to the AUX relays, etc.), you must delete all the nodes EXCEPT the iAquaLink Nodeserver node from the Polyglot Dashboard (not the ISY), restart the nodeserver, and perform the "Discover Devices" procedure again.
2628

27-
For more information regarding this Polyglot Nodeserver, see https://forum.universal-devices.com/topic/28463-polyglot-bond-bridge-nodeserver/.
29+
For more information regarding this Polyglot Nodeserver, see https://forum.universal-devices.com/topic/29262-polyglot-iaqualink-nodeserver/.

iaqua-poly.py

Lines changed: 56 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
IX_DEV_ST_ON = 1
3636
IX_DEV_ST_ENABLED = 3
3737

38+
# Color Light Types and node IDs
39+
DEVICE_COLOR_LIGHT_TYPES = {
40+
"1": "COLOR_LIGHT_JC",
41+
"2": "COLOR_LIGHT_SL",
42+
"4": "COLOR_LIGHT_JL",
43+
"5": "COLOR_LIGHT_IB",
44+
"6": "COLOR_LIGHT_HU",
45+
}
3846

3947
# device labels for system-level devices
4048
# the user can change these in ISY Admin Console, but provide the iAquaLink defaults
@@ -53,9 +61,13 @@
5361
DEVICE_ADDR_SPA_HEAT = "spaht"
5462
DEVICE_ADDR_SOLAR_HEAT = "solarht"
5563

64+
5665
# custom parameter values for this nodeserver
5766
PARAM_USERNAME = "username"
5867
PARAM_PASSWORD = "password"
68+
PARAM_SESSION_TTL = "sessionTTL"
69+
70+
DEFAULT_SESSION_TTL = 43200 # 12 hours
5971

6072
# Node class for devices (pumps and aux relays)
6173
class Device(polyinterface.Node):
@@ -131,7 +143,7 @@ def cmd_dof(self, command):
131143
# Place the controller in active polling mode
132144
self.controller.setActiveMode()
133145

134-
drivers = [{"driver": "ST", "value": 0, "uom": ISY_INDEX_UOM}]
146+
drivers = [{"driver": "ST", "value": IX_DEV_ST_UNKNOWN, "uom": ISY_INDEX_UOM}]
135147
commands = {
136148
"DON": cmd_don,
137149
"DOF": cmd_dof
@@ -268,25 +280,32 @@ class ColorLight(polyinterface.Node):
268280
_lightType = ""
269281

270282
def __init__(self, controller, primary, addr, name, deviceName=None, lightType=None):
271-
super(ColorLight, self).__init__(controller, primary, addr, name)
272283

273-
# override the parent node with the system node (defaults to controller)
274-
self.parent = self.controller.nodes[self.primary]
275-
276284
if deviceName is None:
277285

278286
# retrieve the deviceName and the tempUnit from polyglot custom data
279-
cData = self.controller.getCustomData(addr).split(";")
287+
# Note: use controller and addr parameters instead of self.controller and self.address
288+
# because parent class init() has not been called yet
289+
cData = controller.getCustomData(addr).split(";")
280290
self.deviceName = cData[0]
281291
self._lightType = cData[1]
282292

283293
else:
284294
self.deviceName = deviceName
285295
self._lightType = lightType
286296

287-
# store instance variables in polyglot custom data
288-
cData = ";".join([self.deviceName, self._lightType])
289-
self.controller.addCustomData(addr, cData)
297+
# determine the proper node ID based on the color light type
298+
self.id = DEVICE_COLOR_LIGHT_TYPES.get(self._lightType, "COLOR_LIGHT_JC")
299+
300+
# Call the parent class init
301+
super(ColorLight, self).__init__(controller, primary, addr, name)
302+
303+
# override the parent node with the system node (defaults to controller)
304+
self.parent = self.controller.nodes[self.primary]
305+
306+
# store instance variables in polyglot custom data
307+
cData = ";".join([self.deviceName, self._lightType])
308+
self.controller.addCustomData(addr, cData)
290309

291310
# Turn on the device
292311
def cmd_don(self, command):
@@ -297,8 +316,8 @@ def cmd_don(self, command):
297316
if command.get("value") is None:
298317
value = "1"
299318
else:
300-
# retrieve the effect parameter value for the command and convert to base range (0-19)
301-
value = str(int(command.get("value")) % 20)
319+
# retrieve the effect parameter value for the command
320+
value = str(command.get("value"))
302321

303322
# call the set_effect API
304323
if self.controller.iaConn.setLightEffect(self.parent.serialNum, self.deviceName, value, self._lightType):
@@ -329,7 +348,7 @@ def cmd_dof(self, command):
329348
# Place the controller in active polling mode
330349
self.controller.setActiveMode()
331350

332-
drivers = [{"driver": "ST", "value": -1, "uom": ISY_INDEX_UOM}]
351+
drivers = [{"driver": "ST", "value": IX_DEV_ST_UNKNOWN, "uom": ISY_INDEX_UOM}]
333352
commands = {
334353
"DON": cmd_don,
335354
"DOF": cmd_dof,
@@ -347,29 +366,32 @@ class TempControl(polyinterface.Node):
347366

348367
# Override init to handle temp units
349368
def __init__(self, controller, primary, addr, name, deviceName=None, tempUnit=None):
350-
super(TempControl, self).__init__(controller, primary, addr, name)
351-
352-
# override the parent node with the system node (defaults to controller)
353-
self.parent = self.controller.nodes[self.primary]
354369

355370
if deviceName is None:
356371

357372
# retrieve the deviceName and the tempUnit from polyglot custom data
358-
cData = self.controller.getCustomData(addr).split(";")
373+
# Note: use controller and addr parameters instead of self.controller and self.address
374+
# because parent class init() has not been called yet
375+
cData = controller.getCustomData(addr).split(";")
359376
self.deviceName = cData[0]
360377
self._tempUnit = cData[1]
361378

362379
else:
363380
self.deviceName = deviceName
364381
self._tempUnit = tempUnit
365-
366-
# store instance variables in polyglot custom data
367-
cData = ";".join([self.deviceName, self._tempUnit])
368-
self.controller.addCustomData(addr, cData)
369382

370383
# setup the temperature unit for the node
371384
self.setTempUnit(self._tempUnit)
372385

386+
# Call the parent class init
387+
super(TempControl, self).__init__(controller, primary, addr, name)
388+
389+
# override the parent node with the system node (defaults to controller)
390+
self.parent = self.controller.nodes[self.primary]
391+
392+
# store instance variables in polyglot custom data
393+
cData = ";".join([self.deviceName, self._tempUnit])
394+
self.controller.addCustomData(self.address, cData)
373395

374396
# Setup the termostat node for the correct temperature unit (F or C)
375397
def setTempUnit(self, tempUnit):
@@ -387,12 +409,6 @@ def setTempUnit(self, tempUnit):
387409
if driver["driver"] in ("CLISPH", "CLITEMP"):
388410
driver["uom"] = ISY_TEMP_C_UOM if tempUnit == "C" else ISY_TEMP_F_UOM
389411

390-
self._tempUnit = tempUnit
391-
392-
# store instance variables in polyglot custom data
393-
cData = ";".join([self.deviceName, self._tempUnit])
394-
self.controller.addCustomData(self.address, cData)
395-
396412
# Turn on the heater
397413
def cmd_don(self, command):
398414

@@ -470,7 +486,7 @@ def cmd_set_temp(self, command):
470486
self.controller.setActiveMode()
471487

472488
drivers = [
473-
{"driver": "ST", "value":0, "uom": ISY_INDEX_UOM},
489+
{"driver": "ST", "value": IX_DEV_ST_UNKNOWN, "uom": ISY_INDEX_UOM},
474490
{"driver": "CLITEMP", "value": 0, "uom": ISY_TEMP_F_UOM},
475491
{"driver": "CLISPH", "value": 0, "uom": ISY_TEMP_F_UOM}
476492
]
@@ -734,8 +750,10 @@ def updateNodeStates(self, forceReport=False):
734750
node.setDriver("ST", int(devices[node.deviceName]["subtype"]), True, forceReport)
735751
else:
736752
node.setDriver("ST", translateState(devices[node.deviceName]["state"]), True, forceReport)
737-
else:
753+
elif devices: # Don't change to UNKNOWN state unless device statuses were returned successfully but the node is not in the list
738754
node.setDriver("ST", IX_DEV_ST_UNKNOWN, True, forceReport)
755+
else:
756+
pass # Just leave the state alone if no device statuses were retrieved
739757

740758
drivers = [
741759
{"driver": "ST", "value": 0, "uom": ISY_BOOL_UOM},
@@ -795,8 +813,14 @@ def start(self):
795813
self.addCustomParam({PARAM_USERNAME: "<email address>", PARAM_PASSWORD: "<password>"})
796814
return
797815

816+
# get session TTL, if in the custom parameters
817+
if PARAM_SESSION_TTL in customParams:
818+
sessionTTL = int(customParams[PARAM_SESSION_TTL])
819+
else:
820+
sessionTTL = DEFAULT_SESSION_TTL
821+
798822
# create a connection to the iAqualink cloud service
799-
conn = api.iAqualinkConnection(LOGGER)
823+
conn = api.iAqualinkConnection(sessionTTL=sessionTTL, logger=LOGGER)
800824

801825
# login using the provided credentials
802826
rc = conn.loginToService(userName, password)
@@ -822,7 +846,7 @@ def start(self):
822846
# second pass for device nodes
823847
for addr in self._nodes:
824848
node = self._nodes[addr]
825-
if node["node_def_id"] not in ("CONTROLLER", "BRIDGE"):
849+
if node["node_def_id"] not in ("CONTROLLER", "SYSTEM"):
826850

827851
LOGGER.info("Adding previously saved node - addr: %s, name: %s, type: %s", addr, node["name"], node["node_def_id"])
828852

@@ -831,7 +855,7 @@ def start(self):
831855
self.addNode(Device(self, node["primary"], addr, node["name"]))
832856
elif node["node_def_id"] == "DIMMING_LIGHT":
833857
self.addNode(DimmingLight(self, node["primary"], addr, node["name"]))
834-
elif node["node_def_id"] == "COLOR_LIGHT":
858+
elif node["node_def_id"] in DEVICE_COLOR_LIGHT_TYPES.values():
835859
self.addNode(ColorLight(self, node["primary"], addr, node["name"]))
836860
elif node["node_def_id"] in ("TEMP_CONTROL", "TEMP_CONTROL_C"):
837861
self.addNode(TempControl(self, node["primary"], addr, node["name"]))

0 commit comments

Comments
 (0)