diff --git a/NPM/get_data.py b/NPM/get_data.py index 75eb03b..a694cf1 100755 --- a/NPM/get_data.py +++ b/NPM/get_data.py @@ -1,4 +1,10 @@ ''' + _ _ ____ __ __ + | \ | | _ \| \/ | + | \| | |_) | |\/| | + | |\ | __/| | | | + |_| \_|_| |_| |_| + Script to get NPM values need parameter: port /usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data.py ttyAMA5 diff --git a/NPM/get_data_temp_hum.py b/NPM/get_data_temp_hum.py new file mode 100644 index 0000000..5abe1c9 --- /dev/null +++ b/NPM/get_data_temp_hum.py @@ -0,0 +1,52 @@ +''' + _ _ ____ __ __ + | \ | | _ \| \/ | + | \| | |_) | |\/| | + | |\ | __/| | | | + |_| \_|_| |_| |_| + +Script to get NPM values: ONLY temp and hum +need parameter: port +/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_temp_hum.py ttyAMA5 +''' + +import serial +import requests +import json +import sys + +parameter = sys.argv[1:] # Exclude the script name +#print("Parameters received:") +port='/dev/'+parameter[0] + +ser = serial.Serial( + port=port, + baudrate=115200, + parity=serial.PARITY_EVEN, + stopbits=serial.STOPBITS_ONE, + bytesize=serial.EIGHTBITS, + timeout = 1 +) + +ser.write(b'\x81\x14\x6B') # Temp and humidity command + +while True: + try: + byte_data_temp_hum = ser.readline() + # Decode temperature and humidity values + temperature = int.from_bytes(byte_data_temp_hum[3:5], byteorder='big') / 100.0 + humidity = int.from_bytes(byte_data_temp_hum[5:7], byteorder='big') / 100.0 + + print(f"temp: {temperature}") + print(f"hum: {humidity}") + break + except KeyboardInterrupt: + print("User interrupt encountered. Exiting...") + time.sleep(3) + exit() + except: + # for all other kinds of error, but not specifying which one + print("Unknown error...") + time.sleep(3) + exit() + diff --git a/NPM/get_data_v2.py b/NPM/get_data_v2.py index e38e8f8..b145eea 100644 --- a/NPM/get_data_v2.py +++ b/NPM/get_data_v2.py @@ -48,9 +48,11 @@ ser = serial.Serial( timeout = 0.5 ) +# 1️⃣ Request PM Data (PM1, PM2.5, PM10) + #ser.write(b'\x81\x11\x6E') #data10s ser.write(b'\x81\x12\x6D') #data60s - +time.sleep(0.5) # Small delay to allow the sensor to process the request #print("Start get_data_v2.py script") byte_data = ser.readline() @@ -61,8 +63,9 @@ PM1 = int.from_bytes(byte_data[9:11], byteorder='big')/10 PM25 = int.from_bytes(byte_data[11:13], byteorder='big')/10 PM10 = int.from_bytes(byte_data[13:15], byteorder='big')/10 -# Write command to retrieve temperature and humidity data +# 2️⃣ Request Temperature & Humidity ser.write(b'\x81\x14\x6B') # Temp and humidity command +time.sleep(0.5) # Small delay to allow the sensor to process the request byte_data_temp_hum = ser.readline() # Decode temperature and humidity values diff --git a/config.json.dist b/config.json.dist index 5ff1daa..fd44607 100755 --- a/config.json.dist +++ b/config.json.dist @@ -8,6 +8,7 @@ "loop/SARA_send_data_v2.py": true, "RTC/save_to_db.py": true, "BME280/get_data_v2.py": true, + "envea/read_value_v2.py": true, "deviceID": "XXXX", "deviceName": "NebuleAir-proXXX", "SaraR4_baudrate": 115200, @@ -15,7 +16,6 @@ "NextPM_ports": [ "ttyAMA5" ], - "NextPM_5channels": false, "i2C_sound": false, "i2c_BME": false, "i2c_RTC": false, diff --git a/envea/read_value_loop.py b/envea/read_value_loop.py index bb80f49..443a7ed 100755 --- a/envea/read_value_loop.py +++ b/envea/read_value_loop.py @@ -1,4 +1,10 @@ """ + _____ _ ___ _______ _ + | ____| \ | \ \ / / ____| / \ + | _| | \| |\ \ / /| _| / _ \ + | |___| |\ | \ V / | |___ / ___ \ + |_____|_| \_| \_/ |_____/_/ \_\ + Main loop to gather data from envea Sensors Need to run every minutes diff --git a/envea/read_value_loop_json.py b/envea/read_value_loop_json.py index 7e75850..af88de3 100755 --- a/envea/read_value_loop_json.py +++ b/envea/read_value_loop_json.py @@ -1,4 +1,10 @@ """ + _____ _ ___ _______ _ + | ____| \ | \ \ / / ____| / \ + | _| | \| |\ \ / /| _| / _ \ + | |___| |\ | \ V / | |___ / ___ \ + |_____|_| \_| \_/ |_____/_/ \_\ + Main loop to gather data from Envea Sensors Runs every minute via cron: diff --git a/envea/read_value_v2.py b/envea/read_value_v2.py new file mode 100644 index 0000000..469c381 --- /dev/null +++ b/envea/read_value_v2.py @@ -0,0 +1,121 @@ +""" + _____ _ ___ _______ _ + | ____| \ | \ \ / / ____| / \ + | _| | \| |\ \ / /| _| / _ \ + | |___| |\ | \ V / | |___ / ___ \ + |_____|_| \_| \_/ |_____/_/ \_\ + +Gather data from envea Sensors and store them to the SQlite table +Use the RTC time for the timestamp + +/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py + +""" + +import json +import serial +import time +import traceback +import sqlite3 +from datetime import datetime + +# Connect to the SQLite database +conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db") +cursor = conn.cursor() + +#GET RTC TIME from SQlite +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' + +# Function to load config data +def load_config(config_file): + try: + with open(config_file, 'r') as file: + config_data = json.load(file) + return config_data + except Exception as e: + print(f"Error loading config file: {e}") + return {} + +# Define the config file path +config_file = '/var/www/nebuleair_pro_4g/config.json' + +# Load the configuration data +config = load_config(config_file) + +# Initialize sensors and serial connections +envea_sondes = config.get('envea_sondes', []) +connected_envea_sondes = [sonde for sonde in envea_sondes if sonde.get('connected', False)] +serial_connections = {} + +if connected_envea_sondes: + for device in connected_envea_sondes: + port = device.get('port', 'Unknown') + name = device.get('name', 'Unknown') + 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 + ) + except serial.SerialException as e: + print(f"Error opening serial port for {name}: {e}") + +global data_h2s, data_no2, data_o3 +data_h2s = 0 +data_no2 = 0 +data_o3 = 0 +data_co = 0 +data_nh3 = 0 + +try: + if connected_envea_sondes: + for device in connected_envea_sondes: + name = device.get('name', 'Unknown') + coefficient = device.get('coefficient', 1) + if name in serial_connections: + serial_connection = serial_connections[name] + try: + serial_connection.write( + b"\xFF\x02\x13\x30\x01\x02\x03\x04\x05\x06\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x12\xAF\x88\x03" + ) + data_envea = serial_connection.readline() + if len(data_envea) >= 20: + byte_20 = data_envea[19] * coefficient + if name == "h2s": + data_h2s = byte_20 + elif name == "no2": + data_no2 = byte_20 + elif name == "o3": + data_o3 = byte_20 + except serial.SerialException as e: + print(f"Error communicating with {name}: {e}") +except Exception as e: + print("An error occurred while gathering data:", e) + traceback.print_exc() + + +#print(f" H2S: {data_h2s}, NO2: {data_no2}, O3: {data_o3}") + +#save to sqlite database +try: + cursor.execute(''' + INSERT INTO data_envea (timestamp,h2s, no2, o3, co, nh3) VALUES (?,?,?,?,?,?)''' + , (rtc_time_str,data_h2s,data_no2,data_o3,data_co,data_nh3 )) + + # Commit and close the connection + conn.commit() + + #print("Sensor data saved successfully!") + +except Exception as e: + print(f"Database error: {e}") + + +conn.close() + + diff --git a/html/database.html b/html/database.html index a1b4c07..0af2ff1 100644 --- a/html/database.html +++ b/html/database.html @@ -67,11 +67,10 @@ - - + @@ -93,6 +92,7 @@ + @@ -244,6 +244,16 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "") PM_ch4 (nb/L) PM_ch5 (nb/L) + `; + }else if (table === "data_envea") { + tableHTML += ` + Timestamp + NO2 + H2S + NH3 + CO + O3 + `; } @@ -280,6 +290,16 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "") ${columns[4]} ${columns[5]} + `; + } else if (table === "data_envea") { + tableHTML += ` + ${columns[0]} + ${columns[1]} + ${columns[2]} + ${columns[3]} + ${columns[4]} + ${columns[5]} + `; } diff --git a/html/logs.html b/html/logs.html index 6d76ecd..143d06d 100755 --- a/html/logs.html +++ b/html/logs.html @@ -240,7 +240,7 @@ function getModem_busy_status() { if (response.running) { // Script is running → Show the Bootstrap spinner script_is_running.innerHTML = ` -
+
Modem is busy...
`; diff --git a/loop/SARA_send_data_v2.py b/loop/SARA_send_data_v2.py index 793f6ea..0a8adb7 100644 --- a/loop/SARA_send_data_v2.py +++ b/loop/SARA_send_data_v2.py @@ -40,7 +40,7 @@ CSV PAYLOAD (AirCarto Servers) 8 -> min_noise 9 -> envea_no2 10 -> envea_h2s - 11 -> envea_o3 + 11 -> envea_nh3 12 -> 4G signal quality, 13 -> PM 0.2μm to 0.5μm quantity (Nb/L) 14 -> PM 0.5μm to 1.0μm quantity (Nb/L) @@ -202,10 +202,13 @@ config_file = '/var/www/nebuleair_pro_4g/config.json' config = load_config(config_file) baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4 device_id = config.get('deviceID', '').upper() #device ID en maj -need_to_log = config.get('loop_log', False) #inscription des logs +bme_280_config = config.get('BME280/get_data_v2.py', False) #présence du BME280 +envea_cairsens= config.get('envea/read_value_v2.py', False) send_aircarto = config.get('send_aircarto', True) #envoi sur AirCarto (data.nebuleair.fr) send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot () selected_networkID = config.get('SARA_R4_neworkID', '') +npm_5channel = config.get('NPM/get_data_modbus.py', False) #5 canaux du NPM + modem_config_mode = config.get('modem_config_mode', False) #modem 4G en mode configuration #update device id in the payload json @@ -279,8 +282,9 @@ try: ''' print('

START LOOP

') - print("Getting NPM values") - # Retrieve the last sensor readings + + #NEXTPM + print("➡️Getting NPM values") cursor.execute("SELECT * FROM data_NPM ORDER BY timestamp DESC LIMIT 1") last_row = cursor.fetchone() # Display the result @@ -313,6 +317,74 @@ try: else: print("No data available in the database.") + #NextPM 5 channels + if npm_5channel: + print("➡️Getting NextPM 5 channels values") + cursor.execute("SELECT * FROM data_NPM_5channels ORDER BY timestamp DESC LIMIT 6") + rows = cursor.fetchall() + # Exclude the timestamp column (assuming first column is timestamp) + data_values = [row[1:] for row in rows] # Exclude timestamp + # Compute column-wise average + num_columns = len(data_values[0]) + averages = [round(sum(col) / len(col)) for col in zip(*data_values)] + + # Store averages in specific indices + payload_csv[13] = averages[0] # Channel 1 + payload_csv[14] = averages[1] # Channel 2 + payload_csv[15] = averages[2] # Channel 3 + payload_csv[16] = averages[3] # Channel 4 + payload_csv[17] = averages[4] # Channel 5 + + #BME280 + if bme_280_config: + print("➡️Getting BME280 values") + cursor.execute("SELECT * FROM data_BME280 ORDER BY timestamp DESC LIMIT 1") + last_row = cursor.fetchone() + if last_row: + print("SQLite DB last available row:", last_row) + BME280_temperature = last_row[1] + BME280_humidity = last_row[2] + BME280_pressure = last_row[3] + + #Add data to payload CSV + payload_csv[3] = BME280_temperature + payload_csv[4] = BME280_humidity + payload_csv[5] = BME280_pressure + + #Add data to payload JSON + payload_json["sensordatavalues"].append({"value_type": "BME280_temperature", "value": str(BME280_temperature)}) + payload_json["sensordatavalues"].append({"value_type": "BME280_humidity", "value": str(BME280_humidity)}) + payload_json["sensordatavalues"].append({"value_type": "BME280_pressure", "value": str(BME280_pressure)}) + else: + print("No data available in the database.") + + #envea + if envea_cairsens: + print("➡️Getting envea cairsens values") + cursor.execute("SELECT * FROM data_envea ORDER BY timestamp DESC LIMIT 6") + rows = cursor.fetchall() + # Exclude the timestamp column (assuming first column is timestamp) + data_values = [row[1:] for row in rows] # Exclude timestamp + # Compute column-wise average, ignoring 0 values + averages = [] + for col in zip(*data_values): # Iterate column-wise + filtered_values = [val for val in col if val != 0] # Remove zeros + if filtered_values: + avg = round(sum(filtered_values) / len(filtered_values)) # Compute average + else: + avg = 0 # If all values were zero, store 0 + averages.append(avg) + + # Store averages in specific indices + payload_csv[9] = averages[0] # envea_no2 + payload_csv[10] = averages[1] # envea_h2s + payload_csv[11] = averages[2] # envea_nh3 + + #Add data to payload JSON + payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[0])}) + payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[1])}) + payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NH3", "value": str(averages[2])}) + print("Verify SARA R4 connection") diff --git a/master.py b/master.py index ee80591..b4b4d6d 100644 --- a/master.py +++ b/master.py @@ -84,6 +84,7 @@ SCRIPTS = [ ("RTC/save_to_db.py", 1, 0), # SAVE RTC time every 1 second, no delay ("NPM/get_data_v2.py", 60, 0), # Get NPM data every 60s, no delay ("NPM/get_data_modbus.py", 10, 2), # Get NPM data (modbus 5 channels) every 10s, with 2s delay + ("envea/read_value_v2.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s, with 2s delay ("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 2s delay ("BME280/get_data_v2.py", 120, 0) # Get BME280 data every 120 seconds, no delay ]