Skip to content

Commit

Permalink
Support retrying failed requests and unique cliend_id (#45)
Browse files Browse the repository at this point in the history
* Initial implementation of retrying requests

* Fix logging

* Use async_make_request

* Retry tests

* Expose retry count to gateway async_send_message

* Set max_retries on gateway

* Variable CLIENT_ID (#43)

* Support specifying client_id, otherwise random
* Take client_id at gateway instantiation not host
* Update `MockConnectedGateway` to late connect
* Update tests

* Update to late connect

* Allow setting max_retries at instantiate

* Changes to instantiate. Formatting.

* Make changelogs lists

* Wording.

* Move .decode into getString

* Additional message code notes

* Reorder get/set

* Fixes for python 3.8

* Account for 0 being falsy

* Updates

* Bump version to v0.8.0
  • Loading branch information
dieselrabbit authored Mar 5, 2023
1 parent 3a70135 commit 0cd1967
Show file tree
Hide file tree
Showing 32 changed files with 762 additions and 318 deletions.
93 changes: 44 additions & 49 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ $ pip install screenlogicpy

# Library usage

_New in v0.5.0: The screenlogicpy library has moved over to using asyncio for all network I/O. Relevant methods now require the `async`/`await` syntax._
* _Changed in v0.5.0: The screenlogicpy library has moved over to using asyncio for all network I/O. Relevant methods now require the `async`/`await` syntax._
* _**New in v0.8.0**: Support for Python 3.8 and 3.9 is being phased out across future releases. This will be the last version to support Python 3.8._

The `ScreenLogicGateway` class is the primary interface.

Expand All @@ -24,24 +25,22 @@ from screenlogicpy import ScreenLogicGateway
gateway = ScreenLogicGateway()
```

_Changed in v0.5.0: Instantiating the gateway no longer automatically connects to the protocol adapter or performs an initial update._

_**Changed in v0.7.0:** Passing adapter connection info when instantiating the gateway is deprecated and will be removed in a future release. Connection info should be passed to `async_connect()` instead._
* _Changed in v0.5.0: Instantiating the gateway no longer automatically connects to the protocol adapter or performs an initial update._
* _Changed in v0.7.0: Passing adapter connection info when instantiating the gateway is deprecated and will be removed in a future release. Connection info should be passed to `async_connect()` instead._
* _**Changed in v0.8.0:** Support for passing connection info to gateway constructor is fully deprecated and has been removed. Ability to specify client id used for push subscriptions, and to specify maximum number of times to retry a request has replaced it._

## Connecting to a ScreenLogic Protocol Adapter

Once instantiated, use `async_connect()` to connect and logon to the ScreenLogic protocol adapter.
Once instantiated, use `async_connect()` to connect and login to the ScreenLogic protocol adapter, and gather the pool configuration.

If disconnected, this method may be called without any parameters to reconnect with the previous connection info, or with new parameters to connect to a different host.

```python
success = await gateway.async_connect("192.168.x.x")
```

This method also performs the initial polling of the pool controller configuration.
**Note:** This is the preferred location to provide connection information.

_New in v0.5.0._

_**Changed in v0.7.0:** `async_connect()` now accepts adapter connection info. This supports handling ip changes to the protocol adapter._
* _New in v0.5.0._
* _Changed in v0.7.0: `async_connect()` now accepts adapter connection info. This supports handling ip changes to the protocol adapter._

## Polling the pool state

Expand All @@ -60,15 +59,15 @@ This update consists of sending requests for:

**Warning:** This method is not rate-limited. The calling application is responsible for maintaining reasonable intervals between updates. The ScreenLogic protocol adapter may respond with an error message if too many requests are made too quickly.

_Changed in v0.5.0: This method is now an async coroutine and no longer disconnects from the protocol adapter after polling the data._
* _Changed in v0.5.0: This method is now an async coroutine and no longer disconnects from the protocol adapter after polling the data._

## Subscribing to pool state updates

The preferred method for retrieving updated pool data is to subscribe to updates pushed to the gateway by the ScreenLogic system. This reduces network traffic compared to polling, and improves responsiveness to state changes.

To enable push updates, subscribe to a particular message code using `gateway.async_subscribe_client(callback, message_code)`, passing a callback method to be called when that message is received, and the [message code](#supported-subscribable-messages) to subscribe to. This function returns a callback that can be called to unsubscribe that particular subscription.

`screenlogicpy` will automatically handle subscribing and unsubscribing as a client to the ScreenLogic protocol adapter upon the first callback subscription and last unsub respectively.
The gateway's `ClientManager` will automatically handle subscribing and unsubscribing as a client to the ScreenLogic protocol adapter upon the first callback subscription and last unsub respectively.

```python
from screenlogicpy.const import CODE
Expand All @@ -81,24 +80,24 @@ unsub_method = await gateway.async_subscribe_client(status_updated, CODE.STATUS_

Example in `./examples/async_client.py`

Multiple callbacks can be subscribed to a single message code. Additionally, a single global callback may be subscribed to multiple message codes.
Multiple callbacks can be subscribed to a single message code. Additionally, a single global callback may be subscribed to multiple message codes.
**Note:** Each combination of callback and code will result in a separate unique unsub callback. The calling application is responsible for managing and unsubing all subscribed callbacks as needed.

### Pushed data

The ScreenLogic system does not make all state information for all equipment available via push messages. The two main state update messages that can be subscribed to are:
While the ScreenLogic system does support some push updates, not all state information for all equipment available via push. The two main state update messages that can be subscribed to are:

- General status update containing
- Air and water temperature and heater states
- Basic status indicators such as Freeze mode and active delays
- Circuit states
- Basic chemistry information
- IntelliChem controller status update containing
- Detailed chemistry information
* General status update containing
* Air and water temperature and heater states
* Basic status indicators such as Freeze mode and active delays
* Circuit states
* Basic chemistry information
* IntelliChem controller status update containing
* Detailed chemistry information

The status of any pumps or salt chlorine generators is not included in any push updates. To supplement this, the different data sets can now be requested individually.

**_New in v0.7.0._**
* _New in v0.7.0._

## Polling specific data

Expand All @@ -121,7 +120,7 @@ await gateway.async_get_scg()
Push subscriptions and polling of all or specific data can be used on their own or at the same time.
**Warning:** Some expected data keys may not be present until a full update has been performed. It is recommended that an initial full `async_update()` be preformed to ensure the gateway's data `dict` is fully primed.

**_New in v0.7.0._**
* _New in v0.7.0._

## Using the data

Expand All @@ -133,15 +132,13 @@ data = gateway.get_data()

## Disconnecting

When done, use `async_disconnect()` to close the connection to the protocol adapter.
When done, use `async_disconnect()` to unsubscribe from push updates and close the connection to the protocol adapter.

```python
await gateway.async_disconnect()
```

_New in v0.5.0._

---
* _New in v0.5.0._

## Gateway Discovery

Expand All @@ -153,7 +150,7 @@ The `discovery` module's `async_discover()` function can be used to get a list o
hosts = await discovery.async_discover()
```

_Changed in v0.5.0: This method is now an async coroutine._
* _Changed in v0.5.0: This method is now an async coroutine._

Example in `./examples/async_discovery.py`

Expand All @@ -179,12 +176,12 @@ Full example in `./examples/gateway.py`

The following actions can be performed with methods on the `ScreenLogicGateway` object:

- Set a specific circuit to on or off
- Set a heating mode for a specific body of water (spa/pool)
- Set a target heating temperature for a specific body of water (spa/pool)
- Select various color-enabled lighting options
- Set the chlorinator output levels
- Setting IntelliChem chemistry values
* Set a specific circuit to on or off
* Set a heating mode for a specific body of water (spa/pool)
* Set a target heating temperature for a specific body of water (spa/pool)
* Select various color-enabled lighting options
* Set the chlorinator output levels
* Setting IntelliChem chemistry values

Each method will `return True` if the operation reported no exceptions.
**Note:** The methods do not confirm the requested action is now in effect on the pool controller.
Expand All @@ -197,9 +194,7 @@ A circuit can be requested to be turned on or off with the `async_set_circuit()`
success = await gateway.async_set_circuit(circuitID, circuitState)
```

_Changed in v0.5.0: This method is now an async coroutine._

---
* _Changed in v0.5.0: This method is now an async coroutine._

## Setting a heating mode

Expand All @@ -209,7 +204,7 @@ The desired heating mode can be set per body of water (pool or spa) with `async_
success = await gateway.async_set_heat_mode(body, mode)
```

_Changed in v0.5.0: This method is now an async coroutine._
* _Changed in v0.5.0: This method is now an async coroutine._

## Setting a target temperature

Expand All @@ -229,7 +224,7 @@ Colors or color-shows can be set for compatible color-enable lighting with `asyn
success = await gateway.async_set_color_lights(light_command)
```

_Changed in v0.5.0: This method is now an async coroutine._
* _Changed in v0.5.0: This method is now an async coroutine._

## Setting chlorinator output levels

Expand All @@ -239,7 +234,7 @@ Chlorinator output levels can be set with `async_set_scg_config()`. `async_set_
success = await gateway.async_set_scg_config(pool_output, spa_output)
```

_New in v0.5.0._
* _New in v0.5.0._

## Setting IntelliChem Chemistry values

Expand Down Expand Up @@ -267,7 +262,7 @@ success = await gateway.async_set_chem_data(ph, orp, ch, ta, ca, sa)

**Note:** Only `ph_setpoint` and `orp_setpoint` are settable through the command line.

_New in v0.6.0._
* _New in v0.6.0._

## Handling unsolicited messages

Expand All @@ -276,8 +271,8 @@ To do so, you need to tell the `ScreenLogicGateway` what message code to listen

**Notes:**

- Currently the `ScreenLogicGateway` must be connected to the protocol adapter before registering a handler.
- Registering a handler in this way does not subscribe the gateway to state updates from the ScreenLogic system.
* Currently the `ScreenLogicGateway` must be connected to the protocol adapter before registering a handler.
* Registering a handler in this way does not subscribe the gateway to push state updates from the ScreenLogic system.

**Example:**

Expand All @@ -301,7 +296,7 @@ gateway.remove_async_message_handler(WEATHER_UPDATE_CODE)

Example in `./examples/async_listen.py`

**_New in v0.7.0._**
* _New in v0.7.0._

## Debug Information

Expand All @@ -312,7 +307,7 @@ A debug function is available in the `ScreenLogicGateway` class: `get_debug`. Th
last_responses = gateway.get_debug()
```

_New in v0.5.5._
* _New in v0.5.5._

# Command line

Expand Down Expand Up @@ -513,7 +508,7 @@ screenlogicpy set color-lights [color mode]
Sets a color mode for all color-capable lights configured on the pool controller.
**Note:** `[color mode]` can be either the `int` or `string` representation of a [color mode](#color-modes).

_New in v0.3.0._
* _New in v0.3.0._

#### set `salt-generator, scg`

Expand All @@ -524,7 +519,7 @@ screenlogicpy set salt-generator [pool_pct] [spa_pct]
Sets the chlorinator output levels for the pool and spa. Pentair treats spa output level as a percentage of the pool's output level.
**Note:** `[pool_pct]` can be an `int` between `0`-`100`, or `*` to keep the current value. `[spa_pct]` can be an `int` between `0`-`100`, or `*` to keep the current value.

_New in v0.5.0._
* _New in v0.5.0._

#### set `chem-data, ch`

Expand All @@ -535,7 +530,7 @@ screenlogicpy set chem-data [ph_setpoint] [orp_setpoint]
Sets the pH and/or ORP set points for the IntelliChem system.
**Note:** `[ph_setpoint]` can be a `float` between `7.2`-`7.6`, or `*` to keep the current value. `[orp_setpoint]` can be an `int` between `400`-`800`, or `*` to keep the current value.

_New in v0.6.0._
* _New in v0.6.0._

# Reference

Expand Down
4 changes: 2 additions & 2 deletions examples/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ async def main():
hosts = await discovery.async_discover()

if len(hosts) > 0:
gateway = ScreenLogicGateway(**hosts[0])
await gateway.async_connect()
gateway = ScreenLogicGateway()
await gateway.async_connect(**hosts[0])
await gateway.async_update()
await gateway.async_disconnect()
pprint.pprint(gateway.get_data())
Expand Down
71 changes: 71 additions & 0 deletions notes/12510_get_circuit_def.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Response:

\x10\x00\x00\x00
\x00\x00\x00\x00\x07\x00\x00\x00Generic\x00
\x01\x00\x00\x00\x03\x00\x00\x00Spa\x00
\x02\x00\x00\x00\x04\x00\x00\x00Pool
\x05\x00\x00\x00\x0e\x00\x00\x00Master Cleaner\x00\x00
\x07\x00\x00\x00\x05\x00\x00\x00Light\x00\x00\x00
\x08\x00\x00\x00\x06\x00\x00\x00Dimmer\x00\x00
\t\x00\x00\x00\t\x00\x00\x00SAm Light\x00\x00\x00
\n\x00\x00\x00\t\x00\x00\x00SAL Light\x00\x00\x00
\x0b\x00\x00\x00\n\x00\x00\x00Photon Gen\x00\x00
\x0c\x00\x00\x00\x0b\x00\x00\x00Color wheel\x00
\r\x00\x00\x00\x05\x00\x00\x00Valve\x00\x00\x00
\x0e\x00\x00\x00\x08\x00\x00\x00Spillway
\x0f\x00\x00\x00\r\x00\x00\x00Floor Cleaner\x00\x00\x00
\x10\x00\x00\x00\x0c\x00\x00\x00IntelliBrite
\x11\x00\x00\x00\x0b\x00\x00\x00MagicStream\x00
\x13\x00\x00\x00\n\x00\x00\x00[NOT USED]\x00\x00

10 00 00 00|
Def Count |
16 |
00 00 00 00|07 00 00 00|47 65 6e 65 72 69 63 00
Code |Name Len |Name
0 |7 | G e n e r i c
01 00 00 00|03 00 00 00|53 70 61 00
Code |Name Len |Name
1 |3 | S p a
02 00 00 00|04 00 00 00|50 6f 6f 6c
Code |Name Len |Name
2 |4 | P o o l
05 00 00 00|0e 00 00 00|4d 61 73 74 65 72 20 43 6c 65 61 6e 65 72 00 00
Code |Name Len |Name
5 |14 | M a s t e r C l e a n e r
07 00 00 00|05 00 00 00|4c 69 67 68 74 00 00 00
Code |Name Len |Name
7 |5 | L i g h t
08 00 00 00|06 00 00 00|44 69 6d 6d 65 72 00 00
Code |Name Len |Name
8 |6 | D i m m e r
09 00 00 00|09 00 00 00|53 41 6d 20 4c 69 67 68 74 00 00 00
Code |Name Len |Name
9 |9 | S A m L i g h t
0a 00 00 00|09 00 00 00|53 41 4c 20 4c 69 67 68 74 00 00 00
Code |Name Len |Name
10 |9 | S A L L i g h t
0b 00 00 00|0a 00 00 00|50 68 6f 74 6f 6e 20 47 65 6e 00 00
Code |Name Len |Name
11 |10 | P h o t o n G e n
0c 00 00 00|0b 00 00 00|43 6f 6c 6f 72 20 77 68 65 65 6c 00
Code |Name Len |Name
12 | | C o l o r w h e e l
0d 00 00 00|05 00 00 00|56 61 6c 76 65 00 00 00
Code |Name Len |Name
13 | | V a l v e
0e 00 00 00|08 00 00 00|53 70 69 6c 6c 77 61 79
Code |Name Len |Name
14 | | S p i l l w a y
0f 00 00 00|0d 00 00 00|46 6c 6f 6f 72 20 43 6c 65 61 6e 65 72 00 00 00
Code |Name Len |Name
15 | | F l o o r C l e a n e r
10 00 00 00|0c 00 00 00|49 6e 74 65 6c 6c 69 42 72 69 74 65
Code |Name Len |Name
16 | | I n t e l l i B r i t e
11 00 00 00|0b 00 00 00|4d 61 67 69 63 53 74 72 65 61 6d 00
Code |Name Len |Name
17 | | M a g i c S t r e a m
13 00 00 00|0a 00 00 00|5b 4e 4f 54 20 55 53 45 44 5d 00 00
Code |Name Len |Name
19 | | [ N O T U S E D ]
11 changes: 11 additions & 0 deletions notes/12518_get_circuit_config.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Response:

500
47 00 00 00|01 00 00 00|01 00 00 00|01 00 00 00|01 00 00 00

501
55 00 00 00|00 00 00 00|02 00 00 00|02 00 00 00|00 00 00 00

503
49 00 00 00|10 00 00 00|04 00 00 00|03 00 00 00|00 00 00 00
Name idx |Function |Device ID |Interface |Flags
30 changes: 30 additions & 0 deletions notes/12561_get_circuit_names.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Request:

00 00 00 00|00 00 00 00|19 00 00 00
controller |start_idx |count
0 |0 |25

Official apps request small chunks to not overwhelm the buffers?
00 00 00 00|19 00 00 00|19 00 00 00
controller |start_idx |count
0 |25 |25

00 00 00 00|32 00 00 00|19 00 00 00
controller |start_idx |count
0 |50 |25

Response:
65 00 00 00
count |

07 00 00 00|41 65 72 61 74 6f 72 00
name_size |name
7 | A e r a t o r
0a 00 00 00 41 69 72 20 42 6c 6f 77 65 72 00 00
name_size |name
10 | A i r B l o w e r
05 00 00 00 41 75 78 20 31 00 00 00
name_size |name
5 | A u x 1

etc...
2 changes: 1 addition & 1 deletion screenlogicpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.7.2"
__version__ = "0.8.0"
# flake8: noqa F401
from screenlogicpy.gateway import ScreenLogicGateway
from screenlogicpy.const import ScreenLogicError
Expand Down
Loading

0 comments on commit 0cd1967

Please sign in to comment.