From d9ed08ebe6f507913041344767ccdc4562e12b17 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sat, 21 Jan 2023 17:32:39 -0800 Subject: [PATCH] Add support for encoding/decoding irrigation queue messages (#96) Add support for encoding/decoding irrigation queue messages Issue #40 Using test case examples from https://github.com/donavanbecker/homebridge-rainbird/issues/396 --- pyrainbird/rainbird.py | 49 ++++++++++- pyrainbird/resources/sipcommands.yaml | 21 ++++- tests/testdata/current_queue.yaml | 119 ++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 tests/testdata/current_queue.yaml diff --git a/pyrainbird/rainbird.py b/pyrainbird/rainbird.py index f239755..8bed178 100644 --- a/pyrainbird/rainbird.py +++ b/pyrainbird/rainbird.py @@ -92,11 +92,56 @@ def decode_schedule(data: str, cmd_template: dict[str, Any]) -> dict[str, Any]: return {"data": data} +def decode_queue(data: str, cmd_template: dict[str, Any]) -> dict[str, Any]: + """Decode a queue command.""" + page = int(data[2:4], 16) + rest = data[4:] + if page == 0: + # Currently running program + return { + "program": { + "program": int(rest[0:2], 16), + "running": bool(int(rest[2:4], 16)), + "zonesRemaining": int(rest[4:6], 16), + } + } + + if page == 1: + queue = [] + for i in range(0, 8): + base = i * 8 + program = int(data[base + 4 : base + 6], 16) + zone = int(data[base + 6 : base + 8], 16) + runtime = int(data[base + 8 : base + 12], 16) + if runtime > 0: + runtime = ((runtime & 0xFF00) >> 8) | ((runtime & 0xFF) << 8) + if zone: + queue.append({"program": program, "zone": zone, "seconds": runtime}) + return {"zones": queue} + + if len(data) == 100: + _LOGGER.debug("data=%s", data) + queue = [] + for i in range(0, 8): + base = i * 12 + program = int(data[base + 4 : base + 6], 16) + zone = int(data[base + 6 : base + 8], 16) + runtime = int(data[base + 8 : base + 12], 16) + if runtime > 0: + runtime = ((runtime & 0xFF00) >> 8) | ((runtime & 0xFF) << 8) + if zone: + queue.append({"program": program, "zone": zone, "seconds": runtime}) + return {"zones": queue} + + return {"data": data} + + DEFAULT_DECODER = "decode_template" DECODERS: dict[str, Callable[[str, dict[str, Any]], dict[str, Any]]] = { "decode_template": decode_template, "decode_schedule": decode_schedule, + "decode_queue": decode_queue, } @@ -125,7 +170,9 @@ def encode_command(command_set: dict[str, Any], *args) -> str: """Encode a rainbird tunnelSip command request.""" cmd_code = command_set["command"] if not (length := command_set[LENGTH]): - raise RainbirdCodingException(f"Unable to encode command missing length: {command_set}") + raise RainbirdCodingException( + f"Unable to encode command missing length: {command_set}" + ) if len(args) > length: raise RainbirdCodingException( f"Too many parameters. {length} expected: {command_set}" diff --git a/pyrainbird/resources/sipcommands.yaml b/pyrainbird/resources/sipcommands.yaml index b72c8e1..fbd3a77 100644 --- a/pyrainbird/resources/sipcommands.yaml +++ b/pyrainbird/resources/sipcommands.yaml @@ -104,7 +104,13 @@ ControllerCommands: parameter: 0 response: '01' length: 2 - # CurrentQueueRequest: 3B + CurrentQueueRequest: + command: 3B + response: 'BB' + length: 2 + page: + position: 2 + length: 2 CurrentRainSensorStateRequest: command: 3E response: BE @@ -145,6 +151,15 @@ ControllerCommands: parameterThree: 0 response: '01' length: 4 + page: + position: 2 + length: 2 + zone: + position: 4 + length: 2 + minutes: + position: 6 + length: 2 CombinedControllerStateRequest: command: 4C response: CC @@ -278,7 +293,9 @@ ControllerResponses: delaySetting: position: 2 length: 4 - # CurrentQueueResponse BB + CurrentQueueResponse: + command: BB + decoder: decode_queue CurrentRainSensorStateResponse: command: 'BE' length: 2 diff --git a/tests/testdata/current_queue.yaml b/tests/testdata/current_queue.yaml new file mode 100644 index 0000000..899ad63 --- /dev/null +++ b/tests/testdata/current_queue.yaml @@ -0,0 +1,119 @@ +data: +- 4B000102 +- 014B +- 4B000202 +- 014B +- 4B000302 +- 014B +- 3B00 +- BB000401020000 +- 3B01 +- BB01000176000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B02 +- BB02000278000000000378000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B01 +- BB0100016B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B02 +- BB02000278000000000378000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B01 +- BB01000161000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B02 +- BB02000278000000000378000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B01 +- BB01000157000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +- 3B02 +- BB02000278000000000378000000000000000000000000000000000000000000000000000000000000000000000000000000 +decoded_data: +- type: StackManuallyRunStationRequest + page: 0 + zone: 1 + minutes: 2 +- type: AcknowledgeResponse + commandEcho: 75 +- type: StackManuallyRunStationRequest + page: 0 + zone: 2 + minutes: 2 +- type: AcknowledgeResponse + commandEcho: 75 +- type: StackManuallyRunStationRequest + page: 0 + zone: 3 + minutes: 2 +- type: AcknowledgeResponse + commandEcho: 75 +- type: CurrentQueueRequest + page: 0 +- type: CurrentQueueResponse + program: + program: 4 # Manual + running: True + zonesRemaining: 2 +- type: CurrentQueueRequest + page: 1 +- type: CurrentQueueResponse + zones: + - zone: 1 + program: 0 + seconds: 118 +- type: CurrentQueueRequest + page: 2 +- type: CurrentQueueResponse + zones: + - zone: 2 + program: 0 + seconds: 120 + - zone: 3 + program: 0 + seconds: 120 +- type: CurrentQueueRequest + page: 1 +- type: CurrentQueueResponse + zones: + - zone: 1 + program: 0 + seconds: 107 +- type: CurrentQueueRequest + page: 2 +- type: CurrentQueueResponse + zones: + - zone: 2 + program: 0 + seconds: 120 + - zone: 3 + program: 0 + seconds: 120 +- type: CurrentQueueRequest + page: 1 +- type: CurrentQueueResponse + zones: + - zone: 1 + program: 0 + seconds: 97 +- type: CurrentQueueRequest + page: 2 +- type: CurrentQueueResponse + zones: + - zone: 2 + program: 0 + seconds: 120 + - zone: 3 + program: 0 + seconds: 120 +- type: CurrentQueueRequest + page: 1 +- type: CurrentQueueResponse + zones: + - zone: 1 + program: 0 + seconds: 87 +- type: CurrentQueueRequest + page: 2 +- type: CurrentQueueResponse + zones: + - zone: 2 + program: 0 + seconds: 120 + - zone: 3 + program: 0 + seconds: 120