diff --git a/production_tool/mqtt_bridge.py b/production_tool/mqtt_bridge.py index feca2a7..4e8a198 100644 --- a/production_tool/mqtt_bridge.py +++ b/production_tool/mqtt_bridge.py @@ -416,24 +416,36 @@ def send_el_get(fd, ipv6, tid): serial_write(fd, frame) serial_write(fd, b"\r\n") -def read_erxudp(fd, timeout=15): - """Wait for ERXUDP and return payload as bytearray, or None.""" +ESV_GET_RES = 0x72 # Get response +ESV_GET_SNA = 0x52 # Get response (some or all requested properties unavailable) + + +def read_erxudp(fd, expected_tid, timeout=15): + """Return the response to expected_tid as bytearray, or None. + + Notifications (INF) and late responses to earlier requests are skipped, + and we keep waiting for the real response until the deadline. + """ deadline = time.time() + timeout while time.time() < deadline: line = serial_readline(fd, timeout=max(0.5, deadline - time.time())) - if line is None: + if line is None or not line.startswith("ERXUDP"): continue - if line.startswith("ERXUDP"): - parts = line.split() - # Tail fields are stable: ... - if len(parts) >= 10: - hex_data = parts[-1].strip() - if not hex_data.startswith("1081"): - continue - try: - return bytearray(binascii.unhexlify(hex_data)) - except Exception as e: - log("ERXUDP hex decode error: {}".format(e)) + parts = line.split() + if len(parts) < 9 or not parts[-1].startswith("1081"): + continue + try: + data = bytearray(binascii.unhexlify(parts[-1].strip())) + except Exception as e: + log("ERXUDP hex decode error: {}".format(e)) + continue + if len(data) < 12: + continue + if ((data[2] << 8) | data[3]) != expected_tid: + continue + if data[10] not in (ESV_GET_RES, ESV_GET_SNA): + continue + return data return None # --------------------------------------------------------------------------- @@ -491,7 +503,7 @@ class MQTTClient(object): var_hdr = (b"\x00\x04MQTT" + b"\x04" + struct.pack("B", flags) - + b"\x00\x3C") # keep-alive 60s + + b"\x01\x2C") # keep-alive 300s payload = _encode_str(self.client_id) if self.username: payload += _encode_str(self.username) @@ -735,6 +747,7 @@ def main(): coeff = 1 unit_kwh = 1.0 last_ping = time.time() + timeouts = 0 # consecutive ERXUDP timeouts while True: try: @@ -742,9 +755,10 @@ def main(): led_rgb(0, 0, 255) try: send_el_get(fd, ipv6, tid) + data = read_erxudp(fd, tid, timeout=15) tid = (tid + 1) & 0xFFFF - data = read_erxudp(fd, timeout=15) if data: + timeouts = 0 props = parse_el_response(data) m = decode_measurements(props) m = apply_energy_scale(m, coeff, unit_kwh) @@ -759,6 +773,9 @@ def main(): publish_measurements(mqtt, device_id, m) else: log("No ERXUDP response (timeout)") + timeouts += 1 + if timeouts >= 2: + raise RuntimeError("ERXUDP timeout") finally: led_rgb(*orig_led) @@ -769,8 +786,9 @@ def main(): time.sleep(poll_interval) except Exception as e: - log("Main loop error: {} - reconnecting Wi-SUN in 30s".format(e)) - time.sleep(30) + log("Main loop error: {} - reconnecting Wi-SUN in 5s".format(e)) + timeouts = 0 + time.sleep(5) try: ipv6 = wisun_connect(fd, br_id, br_pwd) log("Wi-SUN reconnected at {}".format(ipv6))