- Increase wait time to 1 second for complete sensor response - Read all available bytes from buffer instead of fixed 32 - Search for frame header (FF 02) anywhere in response data (handles command echo or garbage before actual frame) - Extract frame from header position for correct byte alignment Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
211 lines
8.1 KiB
Python
Executable File
211 lines
8.1 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 complete sensor response (sensor needs time to process and reply)
|
||
time.sleep(1.0)
|
||
|
||
# Read all available data from buffer
|
||
bytes_available = serial_connection.in_waiting
|
||
debug_print(f" ← Bytes available in buffer: {bytes_available}")
|
||
|
||
if bytes_available > 0:
|
||
data_envea = serial_connection.read(bytes_available)
|
||
else:
|
||
# Fallback: try reading with timeout
|
||
data_envea = serial_connection.read(32)
|
||
|
||
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}")
|
||
else:
|
||
debug_print(f" ✗ Frame too short ({len(frame_data)} bytes after header, expected ≥20)")
|
||
else:
|
||
debug_print(f" ✗ No valid frame header (FF 02) found in response")
|
||
|
||
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") |