Updating to new sending format of diy-sender

This commit is contained in:
2026-01-08 21:26:05 +00:00
parent 5b4340fab2
commit d1c1f63cb9
3 changed files with 125 additions and 118 deletions

View File

@@ -193,6 +193,46 @@ def parse_radio_frame(byte_data):
if not byte_data:
return None
try:
sync_index = byte_data.find(b"\x2d")
if sync_index == -1:
return None
if sync_index + 1 >= len(byte_data):
# No room for networkId byte
return None
network_id = byte_data[sync_index + 1]
sync_len = 2
header_start = sync_index + sync_len
if header_start + 4 > len(byte_data):
return None
payload_len, dest_id, sender_id, ctl = struct.unpack_from('<BBBB', byte_data, header_start)
data_start = header_start + 4
data_end = data_start + payload_len
if data_end + 2 > len(byte_data):
# Not enough bytes for data + 2-byte CRC
return None
data = byte_data[data_start:data_end]
crc_bytes = byte_data[data_end:data_end + 2]
return {
'data': data,
'payload_len': payload_len,
'dest_id': dest_id,
'sender_id': sender_id,
'ctl': ctl,
'network_id': network_id,
'crc_bytes': crc_bytes,
'sync_index': sync_index,
'sync_len': sync_len,
}
except Exception:
return None
PAYLOAD_SIZE = 15 # bytes in pool payload
MAGIC1 = 0x42
@@ -271,46 +311,35 @@ def decode_pool_payload(candidate_bytes: bytes, expected_seq: Optional[int] = No
return best
try:
# Find sync 0x2D followed by networkId (second byte)
sync_index = byte_data.find(b"\x2d")
if sync_index == -1:
return None
if sync_index + 1 >= len(byte_data):
# No room for networkId byte
return None
def extract_pool_candidate_bytes(raw_bytes: bytes):
"""Return payload bytes for pool sensors, handling legacy and new radio framing.
network_id = byte_data[sync_index + 1]
sync_len = 2
Tries legacy framed parsing first, then strips a 0xAA preamble and optional
sync bytes (0x39 0x14) used by the new hardware. Falls back to the stripped
raw stream so old payloads continue to work.
"""
if not raw_bytes:
return b"", {"source": "empty"}
header_start = sync_index + sync_len
if header_start + 4 > len(byte_data):
return None
# Legacy framed format (rarely used but kept for compatibility)
frame = parse_radio_frame(raw_bytes)
if frame and frame.get('data'):
return frame['data'], {"source": "legacy_frame", "network_id": frame.get('network_id')}
payload_len, dest_id, sender_id, ctl = struct.unpack_from('<BBBB', byte_data, header_start)
data_start = header_start + 4
data_end = data_start + payload_len
if data_end + 2 > len(byte_data):
# Not enough bytes for data + 2-byte CRC
return None
trimmed = raw_bytes
while trimmed.startswith(b"\xaa"):
trimmed = trimmed[1:]
data = byte_data[data_start:data_end]
crc_bytes = byte_data[data_end:data_end + 2]
# New hardware emits sync bytes; support both 0x39 0x14 and 0xD3 0x91 variants
sync_patterns = [b"\x39\x14", b"\xd3\x91"]
for sync in sync_patterns:
idx = trimmed.find(sync)
if idx != -1 and idx + len(sync) < len(trimmed):
return trimmed[idx + len(sync):], {"source": "sync", "offset": idx, "sync": sync.hex()}
return {
'data': data,
'payload_len': payload_len,
'dest_id': dest_id,
'sender_id': sender_id,
'ctl': ctl,
'network_id': network_id,
'crc_bytes': crc_bytes,
'sync_index': sync_index,
'sync_len': sync_len,
}
except Exception:
return None
# Fallback: use stripped raw stream (old hardware behaviour)
return trimmed, {"source": "raw"}
def refresh_sensor_cache():
"""Refresh the sensor cache from database"""
@@ -469,9 +498,15 @@ def on_message(client, userdata, msg):
malformed_hex_logger.info(f"Pool message missing data: {d}")
return
# Strip optional 'aaaaaa' prefix if present (old format)
if hex_data.startswith('aaaaaa'):
hex_data = hex_data[6:]
# Strip any length of 'aa' preamble (old/new formats)
while hex_data.startswith('aa'):
hex_data = hex_data[2:]
# Some rtl_433 captures occasionally lose the final nibble; drop it to keep hex even-length
if len(hex_data) % 2 == 1:
if LOG_MALFORMED_HEX:
malformed_hex_logger.info(f"Trimming odd-length hex (dropped last nibble): {hex_data}")
hex_data = hex_data[:-1]
try:
byte_data = bytes.fromhex(hex_data)
@@ -483,25 +518,15 @@ def on_message(client, userdata, msg):
warte = ''
return
# Attempt to parse the radio frame first (preamble/sync/header/data/crc)
frame = parse_radio_frame(byte_data)
if frame and frame.get('data'):
print(
f"Parsed radio frame: netId={frame.get('network_id')}, len={frame['payload_len']}, "
f"dest={frame['dest_id']}, sender={frame['sender_id']}, ctl={frame['ctl']}, crc={frame['crc_bytes'].hex()}"
)
candidate_bytes = frame['data']
else:
# Fallback: Drop optional leading 0xAA bytes from hardware and use raw stream
if LOG_MALFORMED_HEX and not frame:
malformed_hex_logger.info(f"Frame parse failed: {byte_data.hex()}")
tmp = byte_data
while tmp.startswith(b"\xaa"):
tmp = tmp[1:]
candidate_bytes = tmp
candidate_bytes, candidate_meta = extract_pool_candidate_bytes(byte_data)
if LOG_MALFORMED_HEX and candidate_meta.get("source") == "raw":
malformed_hex_logger.info(f"Pool using raw bytes (no sync match): {byte_data.hex()}")
print(f"Raw bytes ({len(byte_data)}): {byte_data.hex()}")
print(f"Candidate payload for app decode ({len(candidate_bytes)}): {candidate_bytes.hex()}")
print(
f"Candidate payload ({len(candidate_bytes)}), source={candidate_meta.get('source')}: "
f"{candidate_bytes.hex()}"
)
# Decode payload by sliding-window detection (magic bytes may be missing)
expected_seq = None
@@ -799,6 +824,10 @@ def store_in_db(utc_time, mqtt_name_id, temperature_c, humidity, pressure_rel, b
# Update the sensor's battery level
sensor.battery = battery
# Update last contact time for pool sensors
if mqtt_name == 'pool':
sensor.last_contact = datetime.now(timezone.utc)
# Update the temperature data
if temperature_c is not None: