Files
nebuleair_pro_4g/envea/read_value_v2.py
PaulVua 80bc16fb26 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 <noreply@anthropic.com>
2026-01-23 17:35:16 +01:00

213 lines
8.1 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
_____ _ ___ _______ _
| ____| \ | \ \ / / ____| / \
| _| | \| |\ \ / /| _| / _ \
| |___| |\ | \ V / | |___ / ___ \
|_____|_| \_| \_/ |_____/_/ \_\
Gather data from envea Sensors and store them to the SQlite table
Use the RTC time for the timestamp
This script is run by a service nebuleair-envea-data.service
/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py -d
"""
import json
import serial
import time
import traceback
import sqlite3
from datetime import datetime
import sys
# Set DEBUG to True to enable debug prints, False to disable
DEBUG = False # Change this to False to disable debug output
# You can also control debug via command line argument
if len(sys.argv) > 1 and sys.argv[1] in ['--debug', '-d']:
DEBUG = True
elif len(sys.argv) > 1 and sys.argv[1] in ['--quiet', '-q']:
DEBUG = False
def debug_print(message):
"""Print debug messages only if DEBUG is True"""
if DEBUG:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] {message}")
debug_print("=== ENVEA Sensor Reader Started ===")
# Connect to the SQLite database
try:
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
except Exception as e:
debug_print(f"✗ Failed to connect to database: {e}")
sys.exit(1)
# GET RTC TIME from SQlite
try:
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
row = cursor.fetchone() # Get the first (and only) row
rtc_time_str = row[1] # '2025-02-07 12:30:45'
except Exception as e:
debug_print(f"✗ Failed to get RTC time: {e}")
rtc_time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
debug_print(f" Using system time instead: {rtc_time_str}")
# Fetch connected ENVEA sondes from SQLite config table
try:
cursor.execute("SELECT port, name, coefficient FROM envea_sondes_table WHERE connected = 1")
connected_envea_sondes = cursor.fetchall() # List of tuples (port, name, coefficient)
debug_print(f"✓ Found {len(connected_envea_sondes)} connected ENVEA sensors")
for port, name, coefficient in connected_envea_sondes:
debug_print(f" - {name}: port={port}, coefficient={coefficient}")
except Exception as e:
debug_print(f"✗ Failed to fetch connected sensors: {e}")
connected_envea_sondes = []
serial_connections = {}
if connected_envea_sondes:
debug_print("\n--- Opening Serial Connections ---")
for port, name, coefficient in connected_envea_sondes:
try:
serial_connections[name] = serial.Serial(
port=f'/dev/{port}',
baudrate=9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
debug_print(f"✓ Opened serial port for {name} on /dev/{port}")
except serial.SerialException as e:
debug_print(f"✗ Error opening serial port for {name}: {e}")
else:
debug_print("! No connected ENVEA sensors found in configuration")
# Initialize sensor data variables
global data_h2s, data_no2, data_o3, data_co, data_nh3, data_so2
data_h2s = 0
data_no2 = 0
data_o3 = 0
data_co = 0
data_nh3 = 0
data_so2 = 0
try:
if connected_envea_sondes:
debug_print("\n--- Reading Sensor Data ---")
for port, name, coefficient in connected_envea_sondes:
if name in serial_connections:
serial_connection = serial_connections[name]
try:
debug_print(f"Reading from {name}...")
calculated_value = None
max_retries = 3
for attempt in range(max_retries):
# Flush input buffer to clear any stale data
serial_connection.reset_input_buffer()
# 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()}")
# Wait for sensor response
time.sleep(0.8)
# Read all available data from buffer
bytes_available = serial_connection.in_waiting
debug_print(f" ← Attempt {attempt + 1}: {bytes_available} bytes available")
if bytes_available > 0:
data_envea = serial_connection.read(bytes_available)
else:
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" ✗ Failed to read {name} after {max_retries} attempts")
except serial.SerialException as e:
debug_print(f"✗ Error communicating with {name}: {e}")
else:
debug_print(f"! No serial connection available for {name}")
except Exception as e:
debug_print(f"\n✗ An error occurred while gathering data: {e}")
traceback.print_exc()
# Display all collected data
debug_print(f"\n--- Collected Sensor Data ---")
debug_print(f"H2S: {data_h2s} ppb")
debug_print(f"NO2: {data_no2} ppb")
debug_print(f"O3: {data_o3} ppb")
debug_print(f"CO: {data_co} ppb")
debug_print(f"NH3: {data_nh3} ppb")
debug_print(f"SO2: {data_so2} ppb")
# Save to sqlite database
try:
cursor.execute('''
INSERT INTO data_envea (timestamp, h2s, no2, o3, co, nh3, so2) VALUES (?,?,?,?,?,?,?)'''
, (rtc_time_str, data_h2s, data_no2, data_o3, data_co, data_nh3, data_so2))
# Commit and close the connection
conn.commit()
except Exception as e:
debug_print(f"✗ Database error: {e}")
traceback.print_exc()
# Close serial connections
if serial_connections:
for name, connection in serial_connections.items():
try:
connection.close()
except:
pass
conn.close()
debug_print("\n=== ENVEA Sensor Reader Finished ===\n")