core
core copied to clipboard
Rainbird ESP-ME3 controller does not support calendar
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.
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
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 closeCloses the issue.@home-assistant rename Awesome new titleRenames the issue.@home-assistant reopenReopen the issue.@home-assistant unassign rainbirdRemoves the current integration label and assignees on the issue, add the integration domain after the command.@home-assistant add-label needs-more-informationAdd a label (needs-more-information, problem in dependency, problem in custom component) to the issue.@home-assistant remove-label needs-more-informationRemove 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)
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
Yeah I since realised it was only a calendar entity!
- Yes there is a schedule configured and working on the controller
- Log captured with debug and attached. Couldn't see anything when debug was disabled. home-assistant_rainbird_2024-11-10T01-05-02.650Z.log
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
Managed to get the following from mitmproxy:
Not sure it is decoding all of the data, was getting some errors re: TLS issues
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!
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
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.
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.
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!
Thank you i can see the requestScheduleAndSettings and response.
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.
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.
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.
| 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)
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?)
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.