- Replace readline() with read(32) to avoid truncation at 0x0A bytes - Add reset_input_buffer() to clear stale data before each read - Add initial read to consume echo/acknowledgment from sensor - Add frame header validation (0xFF 0x02) to reject invalid data - Add delays to allow sensor response time Fixes issue where NO2/H2S sensors showed random spikes due to binary data containing newline characters being misinterpreted. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
199 lines
7.7 KiB
Python
Executable File
199 lines
7.7 KiB
Python
Executable File
"""
|
||
_____ _ ___ _______ _
|
||
| ____| \ | \ \ / / ____| / \
|
||
| _| | \| |\ \ / /| _| / _ \
|
||
| |___| |\ | \ 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}...")
|
||
|
||
# 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)
|
||
debug_print(f" → Sent command: {command.hex()}")
|
||
|
||
# Wait for sensor response
|
||
time.sleep(0.5)
|
||
|
||
# Read fixed number of bytes (not readline which stops at 0x0A)
|
||
# First read to consume any echo/acknowledgment
|
||
initial_data = serial_connection.read(serial_connection.in_waiting or 1)
|
||
debug_print(f" ← Initial read: {len(initial_data)} bytes")
|
||
|
||
# Small delay then read the actual response
|
||
time.sleep(0.1)
|
||
data_envea = serial_connection.read(32) # Read fixed 32 bytes
|
||
debug_print(f" ← Received {len(data_envea)} bytes: {data_envea.hex() if data_envea else 'empty'}")
|
||
|
||
# Validate frame: should start with 0xFF 0x02 and have at least 20 bytes
|
||
if len(data_envea) >= 20:
|
||
# Check frame header (0xFF 0x02 expected for valid CAIRSENS response)
|
||
if data_envea[0] == 0xFF and data_envea[1] == 0x02:
|
||
byte_20 = data_envea[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}")
|
||
else:
|
||
debug_print(f" ✗ Invalid frame header: expected FF 02, got {data_envea[0]:02X} {data_envea[1]:02X}")
|
||
else:
|
||
debug_print(f" ✗ Response too short ({len(data_envea)} bytes, expected ≥20)")
|
||
|
||
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") |