From 80bc16fb26e832355f392ed1c1419342bbebbbb4 Mon Sep 17 00:00:00 2001 From: PaulVua Date: Fri, 23 Jan 2026 17:35:16 +0100 Subject: [PATCH] fix(envea): add retry logic for sensor read failures - Retry up to 3 times if no valid frame is received - Reduced wait time per attempt to 0.8s (total max ~3s with retries) - Small delay between retries (0.2s) - Only logs command on first attempt to reduce noise This should eliminate the occasional 0 values caused by timing issues. Co-Authored-By: Claude Opus 4.5 --- envea/read_value_v2.py | 110 +++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 54 deletions(-) diff --git a/envea/read_value_v2.py b/envea/read_value_v2.py index 3af3f34..2775ba0 100755 --- a/envea/read_value_v2.py +++ b/envea/read_value_v2.py @@ -106,67 +106,69 @@ try: try: debug_print(f"Reading from {name}...") - # Flush input buffer to clear any stale data - serial_connection.reset_input_buffer() + calculated_value = None + max_retries = 3 - # Send command to sensor - command = b"\xFF\x02\x13\x30\x01\x02\x03\x04\x05\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x12\xAF\x88\x03" - serial_connection.write(command) - debug_print(f" → Sent command: {command.hex()}") + for attempt in range(max_retries): + # Flush input buffer to clear any stale data + serial_connection.reset_input_buffer() - # Wait for complete sensor response (sensor needs time to process and reply) - time.sleep(1.5) + # Send command to sensor + command = b"\xFF\x02\x13\x30\x01\x02\x03\x04\x05\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x12\xAF\x88\x03" + serial_connection.write(command) + if attempt == 0: + debug_print(f" → Sent command: {command.hex()}") - # Read all available data from buffer - bytes_available = serial_connection.in_waiting - debug_print(f" ← Bytes available in buffer: {bytes_available}") + # Wait for sensor response + time.sleep(0.8) - if bytes_available > 0: - data_envea = serial_connection.read(bytes_available) - else: - # Fallback: try reading with timeout - data_envea = serial_connection.read(32) + # Read all available data from buffer + bytes_available = serial_connection.in_waiting + debug_print(f" ← Attempt {attempt + 1}: {bytes_available} bytes available") - debug_print(f" ← Received {len(data_envea)} bytes: {data_envea.hex() if data_envea else 'empty'}") - - # Find frame start (0xFF 0x02) in received data - may have echo/garbage before it - frame_start = -1 - for i in range(len(data_envea) - 1): - if data_envea[i] == 0xFF and data_envea[i + 1] == 0x02: - frame_start = i - debug_print(f" → Found frame header at position {i}") - break - - if frame_start >= 0: - # Extract frame from header position - frame_data = data_envea[frame_start:] - debug_print(f" → Frame data ({len(frame_data)} bytes): {frame_data.hex()}") - - if len(frame_data) >= 20: - byte_20 = frame_data[19] - raw_value = byte_20 - calculated_value = byte_20 * coefficient - debug_print(f" → Byte 20 value: {raw_value} (0x{raw_value:02X})") - debug_print(f" → Calculated value: {raw_value} × {coefficient} = {calculated_value}") - - if name == "h2s": - data_h2s = calculated_value - elif name == "no2": - data_no2 = calculated_value - elif name == "o3": - data_o3 = calculated_value - elif name == "co": - data_co = calculated_value - elif name == "nh3": - data_nh3 = calculated_value - elif name == "so2": - data_so2 = calculated_value - - debug_print(f" ✓ {name.upper()} = {calculated_value}") + if bytes_available > 0: + data_envea = serial_connection.read(bytes_available) else: - debug_print(f" ✗ Frame too short ({len(frame_data)} bytes after header, expected ≥20)") + data_envea = serial_connection.read(32) + + if len(data_envea) > 0: + debug_print(f" ← Received {len(data_envea)} bytes: {data_envea.hex()}") + + # Find frame start (0xFF 0x02) in received data + frame_start = -1 + for i in range(len(data_envea) - 1): + if data_envea[i] == 0xFF and data_envea[i + 1] == 0x02: + frame_start = i + break + + if frame_start >= 0: + frame_data = data_envea[frame_start:] + if len(frame_data) >= 20: + byte_20 = frame_data[19] + calculated_value = byte_20 * coefficient + debug_print(f" → Found valid frame at position {frame_start}") + debug_print(f" → Byte 20 = {byte_20} × {coefficient} = {calculated_value}") + break # Success, exit retry loop + + debug_print(f" ✗ Attempt {attempt + 1} failed, {'retrying...' if attempt < max_retries - 1 else 'giving up'}") + time.sleep(0.2) + + if calculated_value is not None: + if name == "h2s": + data_h2s = calculated_value + elif name == "no2": + data_no2 = calculated_value + elif name == "o3": + data_o3 = calculated_value + elif name == "co": + data_co = calculated_value + elif name == "nh3": + data_nh3 = calculated_value + elif name == "so2": + data_so2 = calculated_value + debug_print(f" ✓ {name.upper()} = {calculated_value}") else: - debug_print(f" ✗ No valid frame header (FF 02) found in response") + debug_print(f" ✗ Failed to read {name} after {max_retries} attempts") except serial.SerialException as e: debug_print(f"✗ Error communicating with {name}: {e}")