update
This commit is contained in:
@@ -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
|
||||
|
||||
52
NPM/get_data_temp_hum.py
Normal file
52
NPM/get_data_temp_hum.py
Normal file
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
"""
|
||||
_____ _ ___ _______ _
|
||||
| ____| \ | \ \ / / ____| / \
|
||||
| _| | \| |\ \ / /| _| / _ \
|
||||
| |___| |\ | \ V / | |___ / ___ \
|
||||
|_____|_| \_| \_/ |_____/_/ \_\
|
||||
|
||||
Main loop to gather data from envea Sensors
|
||||
Need to run every minutes
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
"""
|
||||
_____ _ ___ _______ _
|
||||
| ____| \ | \ \ / / ____| / \
|
||||
| _| | \| |\ \ / /| _| / _ \
|
||||
| |___| |\ | \ V / | |___ / ___ \
|
||||
|_____|_| \_| \_/ |_____/_/ \_\
|
||||
|
||||
Main loop to gather data from Envea Sensors
|
||||
|
||||
Runs every minute via cron:
|
||||
|
||||
121
envea/read_value_v2.py
Normal file
121
envea/read_value_v2.py
Normal file
@@ -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()
|
||||
|
||||
|
||||
@@ -67,11 +67,10 @@
|
||||
<option value="30">30 dernières</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',getSelectedLimit(),false)">Mesures PM</button>
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button>
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button>
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,6 +92,7 @@
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM',10,true, getStartDate(), getEndDate())">Mesures PM</button>
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',10,true, getStartDate(), getEndDate())">Mesures Temp/Hum</button>
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',10,true, getStartDate(), getEndDate())">Mesures PM (5 canaux)</button>
|
||||
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())">Sonde Cairsens</button>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
@@ -244,6 +244,16 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
||||
<th>PM_ch4 (nb/L)</th>
|
||||
<th>PM_ch5 (nb/L)</th>
|
||||
|
||||
`;
|
||||
}else if (table === "data_envea") {
|
||||
tableHTML += `
|
||||
<th>Timestamp</th>
|
||||
<th>NO2</th>
|
||||
<th>H2S</th>
|
||||
<th>NH3</th>
|
||||
<th>CO</th>
|
||||
<th>O3</th>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -280,6 +290,16 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
||||
<td>${columns[4]}</td>
|
||||
<td>${columns[5]}</td>
|
||||
|
||||
`;
|
||||
} else if (table === "data_envea") {
|
||||
tableHTML += `
|
||||
<td>${columns[0]}</td>
|
||||
<td>${columns[1]}</td>
|
||||
<td>${columns[2]}</td>
|
||||
<td>${columns[3]}</td>
|
||||
<td>${columns[4]}</td>
|
||||
<td>${columns[5]}</td>
|
||||
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ function getModem_busy_status() {
|
||||
if (response.running) {
|
||||
// Script is running → Show the Bootstrap spinner
|
||||
script_is_running.innerHTML = `
|
||||
<div class="spinner-border text-danger" role="status">
|
||||
<div class="spinner-border spinner-border-sm text-danger" role="status">
|
||||
<span class="visually-hidden">Modem is busy...</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -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('<h3>START LOOP</h3>')
|
||||
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")
|
||||
|
||||
|
||||
@@ -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
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user