core icon indicating copy to clipboard operation
core copied to clipboard

Rainbird ESP-ME3 controller does not support calendar

Open mitch-1211 opened this issue 1 year ago • 15 comments

The problem

The controller entity doesn’t synchronise with the status of the physical ESP-ME3 controller. The status of the controller always shows as off, even when irrigation is running. No events show on the calendar.

IMG_4137

Individual zones work as expected.

What version of Home Assistant Core has the issue?

core-2024.10.4

What was the last working version of Home Assistant Core?

No response

What type of installation are you running?

Home Assistant OS

Integration causing the issue

Rainbird

Link to integration documentation on our website

https://www.home-assistant.io/integrations/rainbird

Diagnostics information

No response

Example YAML snippet

No response

Anything in the logs that might be useful for us?

No response

Additional information

No response

mitch-1211 avatar Nov 09 '24 23:11 mitch-1211

Hey there @konikvranik, @allenporter, mind taking a look at this issue as it has been labeled with an integration (rainbird) you are listed as a code owner for? Thanks!

Code owner commands

Code owners of rainbird can trigger bot actions by commenting:

  • @home-assistant close Closes the issue.
  • @home-assistant rename Awesome new title Renames the issue.
  • @home-assistant reopen Reopen the issue.
  • @home-assistant unassign rainbird Removes the current integration label and assignees on the issue, add the integration domain after the command.
  • @home-assistant add-label needs-more-information Add a label (needs-more-information, problem in dependency, problem in custom component) to the issue.
  • @home-assistant remove-label needs-more-information Remove a label (needs-more-information, problem in dependency, problem in custom component) on the issue.

(message by CodeOwnersMention)


rainbird documentation rainbird source (message by IssueLinks)

home-assistant[bot] avatar Nov 09 '24 23:11 home-assistant[bot]

To clarify: The controller entity is a calendar only (not reflecting the status of the controller, but only its schedule).

  • Do you have a schedule configured or are these turning on and off manually?
  • Is there anything relevant in the logs related to rainbird?
  • Can you capture logs with debug mode enabled and reload the integration? This will capture the schedule/calendar info

allenporter avatar Nov 10 '24 00:11 allenporter

Yeah I since realised it was only a calendar entity!

mitch-1211 avatar Nov 10 '24 01:11 mitch-1211

This section of the log appears to attempt to request the schedule but the device rejects the request:

2024-11-10 11:00:28.980 DEBUG (MainThread) [pyrainbird.async_client] Returned cached result for key 'AvailableStationsRequest-(0,)'
2024-11-10 11:00:28.981 DEBUG (MainThread) [pyrainbird.async_client] Loading schedule for 22 zones
2024-11-10 11:00:28.981 DEBUG (MainThread) [pyrainbird.async_client] Sending schedule commands: ['00', '0010', '0011', '0012', '0013', '0060', '0061', '0062', '0063', '0080', '0081', '0082', '0083', '0084', '0085', '0086', '0087', '0088', '0089', '008a']
2024-11-10 11:00:28.981 DEBUG (MainThread) [pyrainbird.async_client] Request (RetrieveScheduleRequest): 200000
2024-11-10 11:00:28.981 DEBUG (MainThread) [pyrainbird.async_client] Request: {"id": 1731200428.981365, "jsonrpc": "2.0", "method": "tunnelSip", "params": {"data": "200000", "length": 3}}
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:28.984 DEBUG (MainThread) [homeassistant.components.rainbird.switch] coordinator.unique_id=94:e6:86:4d:0e:54
2024-11-10 11:00:29.096 DEBUG (MainThread) [pyrainbird.async_client] Response: {"id":1731200428.981365,"jsonrpc":"2.0","result":{"data":"002001","length":3}}
2024-11-10 11:00:29.096 DEBUG (MainThread) [pyrainbird.async_client] Response from line: 002001
2024-11-10 11:00:29.096 DEBUG (MainThread) [pyrainbird.async_client] Response: {'type': 'NotAcknowledgeResponse', 'commandEcho': 32, 'NAKCode': 1}
2024-11-10 11:00:29.096 DEBUG (MainThread) [pyrainbird.async_client] Request (RetrieveScheduleRequest): 200010
2024-11-10 11:00:29.096 DEBUG (MainThread) [pyrainbird.async_client] Request: {"id": 1731200429.0968037, "jsonrpc": "2.0", "method": "tunnelSip", "params": {"data": "200010", "length": 3}}
2024-11-10 11:00:29.225 DEBUG (MainThread) [pyrainbird.async_client] Response: {"id":1731200429.0968037,"jsonrpc":"2.0","result":{"data":"002001","length":3}}
2024-11-10 11:00:29.226 DEBUG (MainThread) [pyrainbird.async_client] Response from line: 002001
2024-11-10 11:00:29.226 DEBUG (MainThread) [pyrainbird.async_client] Response: {'type': 'NotAcknowledgeResponse', 'commandEcho': 32, 'NAKCode': 1}
2024-11-10 11:00:29.226 DEBUG (MainThread) [pyrainbird.async_client] Request (RetrieveScheduleRequest): 200011
2024-11-10 11:00:29.226 DEBUG (MainThread) [pyrainbird.async_client] Request: {"id": 1731200429.2266047, "jsonrpc": "2.0", "method": "tunnelSip", "params": {"data": "200011", "length": 3}}
2024-11-10 11:00:29.335 DEBUG (MainThread) [pyrainbird.async_client] Response: {"id":1731200429.2266047,"jsonrpc":"2.0","result":{"data":"002001","length":3}}
2024-11-10 11:00:29.336 DEBUG (MainThread) [pyrainbird.async_client] Response from line: 002001
2024-11-10 11:00:29.336 DEBUG (MainThread) [pyrainbird.async_client] Response: {'type': 'NotAcknowledgeResponse', 'commandEcho': 32, 'NAKCode': 1}
2024-11-10 11:00:29.336 DEBUG (MainThread) [pyrainbird.async_client] Request (RetrieveScheduleRequest): 200012
2024-11-10 11:00:29.336 DEBUG (MainThread) [pyrainbird.async_client] Request: {"id": 1731200429.3364153, "jsonrpc": "2.0", "method": "tunnelSip", "params": {"data": "200012", "length": 3}}
2024-11-10 11:00:29.502 DEBUG (MainThread) [pyrainbird.async_client] Response: {"id":1731200429.3364153,"jsonrpc":"2.0","result":{"data":"002001","length":3}}
2024-11-10 11:00:29.502 DEBUG (MainThread) [pyrainbird.async_client] Response from line: 002001
2024-11-10 11:00:29.502 DEBUG (MainThread) [pyrainbird.async_client] Response: {'type': 'NotAcknowledgeResponse', 'commandEcho': 32, 'NAKCode': 1}
2024-11-10 11:00:29.502 DEBUG (MainThread) [pyrainbird.async_client] Request (RetrieveScheduleRequest): 200013
2024-11-10 11:00:29.502 DEBUG (MainThread) [pyrainbird.async_client] Request: {"id": 1731200429.5028653, "jsonrpc": "2.0", "method": "tunnelSip", "params": {"data": "200013", "length": 3}}
2024-11-10 11:00:29.613 DEBUG (MainThread) [pyrainbird.async_client] Response: {"id":1731200429.5028653,"jsonrpc":"2.0","result":{"data":"002001","length":3}}
2024-11-10 11:00:29.613 DEBUG (MainThread) [pyrainbird.async_client] Response from line: 002001
2024-11-10 11:00:29.613 DEBUG (MainThread) [pyrainbird.async_client] Response: {'type': 'NotAcknowledgeResponse', 'commandEcho': 32, 'NAKCode': 1}
2024-11-10 11:00:29.613 DEBUG (MainThread) [pyrainbird.async_client] Request (RetrieveScheduleRequest): 200060
2024-11-10 11:00:29.614 DEBUG (MainThread) [pyrainbird.async_client] Request: {"id": 1731200429.6140583, "jsonrpc": "2.0", "method": "tunnelSip", "params": {"data": "200060", "length": 3}}

So this indicates it does not know its format.

In order to make this work we need to understand how the device expects to see these commands, similar to what was done in https://github.com/allenporter/pyrainbird/issues/315

allenporter avatar Nov 10 '24 01:11 allenporter

Managed to get the following from mitmproxy:

logs.txt flows.txt

Not sure it is decoding all of the data, was getting some errors re: TLS issues

mitch-1211 avatar Nov 10 '24 02:11 mitch-1211

If you need further data I can try and give this a go too, as I am also keen to get the calendar working correctly. Thanks for your hard work @allenporter , much appreciated!

Pilotrysa avatar Dec 11 '24 12:12 Pilotrysa

I don't really know how to interpret the results above as they don't look how i was expected. More data from mitm proxy in the rainbird decoded format would be helpful

allenporter avatar Dec 14 '24 02:12 allenporter

I have a blank calendar for my Rainbird RC2 controller as well (other functions working correctly).

@allenporter is it difficult to obtain Rainbird decoded data from mitm proxy? I also would be happy to try and obtain them, but I'm not familiar with mitm proxy / Rainbirds setup.

jz-v avatar Mar 14 '25 01:03 jz-v

https://github.com/allenporter/pyrainbird/blob/main/CONTRIBUTING.md explains how to do this. We also have https://github.com/allenporter/pyrainbird/issues/178 as an example. we can also followup in that repo.

allenporter avatar Mar 14 '25 02:03 allenporter

I finally found some time to sit down and work out mitm enough to get a HAR file for this. Let me know if this suffices for what you need @allenporter and thanks again for all your hard work!

Rainbird HAR.zip

Pilotrysa avatar May 10 '25 23:05 Pilotrysa

Thank you i can see the requestScheduleAndSettings and response.

allenporter avatar May 11 '25 19:05 allenporter

If you want to share any of the steps you've learned at https://github.com/allenporter/pyrainbird/blob/main/CONTRIBUTING.md on how to do this I am sure others would welcome.

allenporter avatar May 11 '25 19:05 allenporter

This trace does not contain communication with the local device so it does not contain any of the status or schedule information. I think we need to capture commands sent to the device and not just the cloud.

allenporter avatar May 11 '25 20:05 allenporter

I was able to find the schedule in the UniversalMessageTransportResponse format but i don't know the details of the format.

            "schedule": {
                "0C200001000805000000000C0000000000050000000E00070B011500020000000000001500": "8C200000000C00000000000805000000000500FFFF6700080B0115000200000000000015000400000000100E000018150000201C000000000000302A000018150000282300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
                "0C200001000805000000000C0000000000050000000E00070B011500020100010000001500": "8C200000000C00000000000805000000000500FFFF6700080B0115000201000100000015000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
                "0C200001000805000000000C0000000000050000001100070B020A0001000015000B000100001500": "8C200000000C00000000000805000000000500FFFF3F00080B020A00010000150001000000000000000000000000000000000000000000000B0001000015000100000000000000000000000000000000000000000000",
                "0C200001000805000000000C0000000000050000000E00070B011500020200020000001500": "8C200000000C00000000000805000000000500FFFF6700080B01150002020002000000150004B0040000000000000000000000000000600900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
                "0C200001000805000000000C0000000000050000001C00070B0312000100000300110001000003001000020000030000000600": "8C200000000C00000000000805000000000500FFFF4300080B0312000100000300010404030311000100000300010100000210000200000300000006000100000000000000010101010101010000000000000001010101010101",
                "0C200001000805000000000C0000000000050000001700070B041400000D00001800010000030013000100000300": "8C200000000C00000000000805000000000500FFFF2900080B0414000001000D0000010018000100000300022D00320028003500130001000003000104040400",
                "0C200001000805000000000C0000000000050000000E00070B011500020300030000001500": "8C200000000C00000000000805000000000500FFFF6700080B0115000203000300000015000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
                "0C200001000805000000000C0000000000050000001200070B011D0003000003000000000000000500": "8C200000000C00000000000805000000000500FFFF4300080B011D0003000003000000000000000500022C01A005A005A005A005A005B400A005A005A005A005A0056801A005A005A005A005A005A005A005A005A005A005A005",
            },

What could also be helpful is to capture this a few times with different schedules e.g. schedule on, schedule off, schedule set to some specific settings. That may be enough to figure out how to map out that response type.

allenporter avatar May 11 '25 20:05 allenporter

Bytes (hex) Comment
0C 20 Command ID 0x200C → “Get/Set Program Schedule”. In replies Rain Bird just sets bit 7, so the response starts 8C 20 (0x80 higher) to become 0x208C – same command, but response flag.
00 01 Program number (little‑endian). 01 00 = Program A, 02 00 = Program B, etc.
00 08 05 00 Always seen on this model when a schedule request is made. 0x08 seems to be a fixed sub‑class for “Program items”, and 0x05 is the object type “run time/start time record”.
…payload… One or more 8‑byte blocks, each describing a single start‑time / run‑time / zone.
15 00 Every request ends with the two‑byte terminator 0x0015.

Decoding a single 8‑byte schedule record EE 00 HH MM ZZ RR DD TT

Byte(s) Meaning (little‑endian unless noted)
EE 00 Run‑time in minutes (0x000E = 14 min, 0x001C = 28 min, …)
HH Start hour (24 h)
MM Start minute
ZZ Zone/station number (1 – 22 on the ESP‑ME3)
RR Repeat slot index (0, 1, 2…). For this controller it’s always 00 in manual schedules.
DD Day‑of‑week bit‑mask (1 = Sun, 2 = Mon, 4 = Tue … 64 = Sat). Example 0x15 = 0b010101 → Mon / Wed / Fri.
TT Trigger type: 00 = “weekly days”, 01 = “odd”, 02 = “even”, 03 = “cyclic‑every‑X‑days”. You can see this vary in your third and fifth samples.

Here’s a decoder

# rainbird_decode.py
# Reverse‑engineered decoder for Rain Bird ESP‑ME3 “Get/Set Program Schedule” (command 0x200C)

DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
TRIGGER_MAP = {
    0: 'Weekly days',
    1: 'Odd days',
    2: 'Even days',
    3: 'Cyclic interval'
}


def decode_daymask(mask: int) -> str:
    """Return a comma‑separated list of day names from Rain Bird’s bit mask."""
    return ','.join(d for i, d in enumerate(DAY_NAMES) if mask & (1 << i)) or '—'


def decode_records(buf: bytes):
    """
    Scan the payload for 8‑byte schedule records and yield decoded dicts.
    Heuristics make sure we ignore header / padding bytes.
    """
    i = 0
    while i <= len(buf) - 8:
        runtime = int.from_bytes(buf[i:i + 2], 'little')
        hour, minute, zone = buf[i + 2], buf[i + 3], buf[i + 4]
        dow_mask, trig = buf[i + 6], buf[i + 7]

        # Tight checks so we don’t pick up random bytes
        if (
            1 <= runtime <= 240 and        # 1 – 240 min (Rain Bird max 4 h)
            0 <= hour <= 23 and
            0 <= minute <= 59 and
            1 <= zone <= 22 and           # ESP‑ME3 supports up to 22 stations
            dow_mask <= 0x7F and
            trig <= 3
        ):
            yield {
                'offset': i,
                'zone': zone,
                'start': f'{hour:02}:{minute:02}',
                'run_min': runtime,
                'days': decode_daymask(dow_mask),
                'trigger': TRIGGER_MAP.get(trig, f'Unknown({trig})')
            }
            i += 8       # jump to the next possible record
        else:
            i += 1       # slide window by one byte and keep scanning


def decode_message(hex_str: str):
    """Decode an entire 0x200C request/response hex string."""
    return list(decode_records(bytes.fromhex(hex_str)))


# --------------------------------------------------------------------
# Demo / CLI convenience
if __name__ == '__main__':
    SAMPLE = (
        "0C200001000805000000000C0000000000050000000E00070B011500020000000000001500"
    )
    print("Decoding sample:")
    for rec in decode_message(SAMPLE):
        print(rec)

suprawsmninja avatar May 11 '25 23:05 suprawsmninja

We probably should move this conversation to an issue in the pyrainbird repo so i've created https://github.com/allenporter/pyrainbird/issues/481

Some of that looks right, but still some details missing. We can try to start decoding like the other commands here https://github.com/allenporter/pyrainbird/blob/main/pyrainbird/resources/sipcommands.yaml (e.g. that's really all only for a single program?)

allenporter avatar May 14 '25 14:05 allenporter

There hasn't been any activity on this issue recently. Due to the high number of incoming GitHub notifications, we have to clean some of the old issues, as many of them have already been resolved with the latest updates. Please make sure to update to the latest Home Assistant version and check if that solves the issue. Let us know if that works for you by adding a comment 👍 This issue has now been marked as stale and will be closed if no further activity occurs. Thank you for your contributions.