This commit is contained in:
Your Name
2026-01-05 15:50:35 +00:00
parent 62ef47aa67
commit a38ce79555
4 changed files with 365 additions and 117 deletions

View File

@@ -14,9 +14,9 @@ import serial
import requests
import json
import sys
import time
parameter = sys.argv[1:] # Exclude the script name
#print("Parameters received:")
port='/dev/'+parameter[0]
ser = serial.Serial(
@@ -34,20 +34,39 @@ ser.write(b'\x81\x11\x6E') #data10s
while True:
try:
byte_data = ser.readline()
#print(byte_data)
# Convert raw data to hex string for debugging
raw_hex = byte_data.hex() if byte_data else ""
# Check if we received data
if not byte_data or len(byte_data) < 15:
data = {
'PM1': 0.0,
'PM25': 0.0,
'PM10': 0.0,
'sleep': 0,
'degradedState': 0,
'notReady': 0,
'heatError': 0,
't_rhError': 0,
'fanError': 0,
'memoryError': 0,
'laserError': 0,
'raw': raw_hex,
'message': f"No data received or incomplete frame (length: {len(byte_data)})"
}
json_data = json.dumps(data)
print(json_data)
break
stateByte = int.from_bytes(byte_data[2:3], byteorder='big')
Statebits = [int(bit) for bit in bin(stateByte)[2:].zfill(8)]
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
#print(f"State: {Statebits}")
#print(f"PM1: {PM1}")
#print(f"PM25: {PM25}")
#print(f"PM10: {PM10}")
#create JSON
# Create JSON with raw data and status message
data = {
'capteurID': 'nebuleairpro1',
'sondeID':'USB2',
'PM1': PM1,
'PM25': PM25,
'PM10': PM10,
@@ -58,18 +77,50 @@ while True:
't_rhError': Statebits[4],
'fanError': Statebits[5],
'memoryError': Statebits[6],
'laserError' : Statebits[7]
'laserError': Statebits[7],
'raw': raw_hex,
'message': 'OK' if sum(Statebits[1:]) == 0 else 'Sensor error detected'
}
json_data = json.dumps(data)
print(json_data)
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...")
data = {
'PM1': 0.0,
'PM25': 0.0,
'PM10': 0.0,
'sleep': 0,
'degradedState': 0,
'notReady': 0,
'heatError': 0,
't_rhError': 0,
'fanError': 0,
'memoryError': 0,
'laserError': 0,
'raw': '',
'message': 'User interrupt encountered'
}
print(json.dumps(data))
time.sleep(3)
exit()
except Exception as e:
data = {
'PM1': 0.0,
'PM25': 0.0,
'PM10': 0.0,
'sleep': 0,
'degradedState': 0,
'notReady': 0,
'heatError': 0,
't_rhError': 0,
'fanError': 0,
'memoryError': 0,
'laserError': 0,
'raw': '',
'message': f'Error: {str(e)}'
}
print(json.dumps(data))
time.sleep(3)
exit()

177
NPM/get_data_modbus_v2_1.py Normal file
View File

@@ -0,0 +1,177 @@
'''
_ _ ____ __ __
| \ | | _ \| \/ |
| \| | |_) | |\/| |
| |\ | __/| | | |
|_| \_|_| |_| |_|
Script to get NPM data via Modbus
Improved version with data stream lenght check
/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_v3.py
Modbus RTU
[Slave Address][Function Code][Starting Address][Quantity of Registers][CRC]
Pour récupérer
les concentrations en PM1, PM10 et PM2.5 (a partir du registre 0x38)
les 5 cannaux
la température et l'humidité à l'intérieur du capteur
Donnée actualisée toutes les 10 secondes
Request
\x01\x03\x00\x38\x00\x55\...\...
\x01 Slave Address (slave device address)
\x03 Function code (read multiple holding registers)
\x00\x38 Starting Address (The request starts reading from holding register address x38 or 56)
\x00\x55 Quantity of Registers (Requests to read x55 or 85 consecutive registers starting from address 56)
\...\... Cyclic Redundancy Check (checksum )
'''
import serial
import requests
import json
import sys
import crcmod
import sqlite3
import time
# Connect to the SQLite database
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
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 {}
# Load the configuration data
npm_solo_port = "/dev/ttyAMA5" #port du NPM solo
#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'
# Initialize serial port
ser = serial.Serial(
port=npm_solo_port,
baudrate=115200,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=2
)
# Define Modbus CRC-16 function
crc16 = crcmod.predefined.mkPredefinedCrcFun('modbus')
# Request frame without CRC
data = b'\x01\x03\x00\x38\x00\x55'
# Calculate and append CRC
crc = crc16(data)
crc_low = crc & 0xFF
crc_high = (crc >> 8) & 0xFF
request = data + bytes([crc_low, crc_high])
# Clear serial buffer before sending
ser.flushInput()
# Send request
ser.write(request)
time.sleep(0.2) # Wait for sensor to respond
# Read response
response_length = 2 + 1 + (2 * 85) + 2 # Address + Function + Data + CRC
byte_data = ser.read(response_length)
# Validate response length
if len(byte_data) < response_length:
print("[ERROR] Incomplete response received:", byte_data.hex())
exit()
# Verify CRC
received_crc = int.from_bytes(byte_data[-2:], byteorder='little')
calculated_crc = crc16(byte_data[:-2])
if received_crc != calculated_crc:
print("[ERROR] CRC check failed! Corrupted data received.")
exit()
# Convert response to hex for debugging
formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data)
#print("Response:", formatted)
# Extract and print PM values
def extract_value(byte_data, register, scale=1, single_register=False, round_to=None):
REGISTER_START = 56
offset = (register - REGISTER_START) * 2 + 3
if single_register:
value = int.from_bytes(byte_data[offset:offset+2], byteorder='big')
else:
lsw = int.from_bytes(byte_data[offset:offset+2], byteorder='big')
msw = int.from_bytes(byte_data[offset+2:offset+4], byteorder='big')
value = (msw << 16) | lsw
value = value / scale
if round_to == 0:
return int(value)
elif round_to is not None:
return round(value, round_to)
else:
return value
pm1_10s = extract_value(byte_data, 56, 1000, round_to=1)
pm25_10s = extract_value(byte_data, 58, 1000, round_to=1)
pm10_10s = extract_value(byte_data, 60, 1000, round_to=1)
#print("10 sec concentration:")
#print(f"PM1: {pm1_10s}")
#print(f"PM2.5: {pm25_10s}")
#print(f"PM10: {pm10_10s}")
# Extract values for 5 channels
channel_1 = extract_value(byte_data, 128, round_to=0) # 0.2 - 0.5μm
channel_2 = extract_value(byte_data, 130, round_to=0) # 0.5 - 1.0μm
channel_3 = extract_value(byte_data, 132, round_to=0) # 1.0 - 2.5μm
channel_4 = extract_value(byte_data, 134, round_to=0) # 2.5 - 5.0μm
channel_5 = extract_value(byte_data, 136, round_to=0) # 5.0 - 10.0μm
#print(f"Channel 1 (0.2->0.5): {channel_1}")
#print(f"Channel 2 (0.5->1.0): {channel_2}")
#print(f"Channel 3 (1.0->2.5): {channel_3}")
#print(f"Channel 4 (2.5->5.0): {channel_4}")
#print(f"Channel 5 (5.0->10.): {channel_5}")
# Retrieve relative humidity from register 106 (0x6A)
relative_humidity = extract_value(byte_data, 106, 100, single_register=True)
# Retrieve temperature from register 106 (0x6A)
temperature = extract_value(byte_data, 107, 100, single_register=True)
#print(f"Internal Relative Humidity: {relative_humidity} %")
#print(f"Internal temperature: {temperature} °C")
cursor.execute('''
INSERT INTO data_NPM_5channels (timestamp,PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5) VALUES (?,?,?,?,?,?)'''
, (rtc_time_str,channel_1,channel_2,channel_3,channel_4,channel_5))
cursor.execute('''
INSERT INTO data_NPM (timestamp,PM1, PM25, PM10, temp_npm, hum_npm) VALUES (?,?,?,?,?,?)'''
, (rtc_time_str,pm1_10s,pm25_10s,pm10_10s,temperature,relative_humidity ))
# Commit and close the connection
conn.commit()
conn.close()

View File

@@ -29,6 +29,8 @@ Request
\x00\x55 Quantity of Registers (Requests to read x55 or 85 consecutive registers starting from address 56)
\...\... Cyclic Redundancy Check (checksum )
MAJ 2026 --> renvoie des 0 si pas de réponse du NPM
'''
import serial
import requests
@@ -59,6 +61,19 @@ 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'
# Initialize default error values
pm1_10s = 0
pm25_10s = 0
pm10_10s = 0
channel_1 = 0
channel_2 = 0
channel_3 = 0
channel_4 = 0
channel_5 = 0
relative_humidity = 0
temperature = 0
try:
# Initialize serial port
ser = serial.Serial(
port=npm_solo_port,
@@ -94,8 +109,8 @@ byte_data = ser.read(response_length)
# Validate response length
if len(byte_data) < response_length:
print("[ERROR] Incomplete response received:", byte_data.hex())
exit()
print(f"[ERROR] Incomplete response received: {byte_data.hex()}")
raise Exception("Incomplete response")
# Verify CRC
received_crc = int.from_bytes(byte_data[-2:], byteorder='little')
@@ -103,7 +118,7 @@ calculated_crc = crc16(byte_data[:-2])
if received_crc != calculated_crc:
print("[ERROR] CRC check failed! Corrupted data received.")
exit()
raise Exception("CRC check failed")
# Convert response to hex for debugging
formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data)
@@ -161,8 +176,14 @@ temperature = extract_value(byte_data, 107, 100, single_register=True)
#print(f"Internal Relative Humidity: {relative_humidity} %")
#print(f"Internal temperature: {temperature} °C")
ser.close()
except Exception as e:
print(f"[ERROR] Sensor communication failed: {e}")
# Variables already set to -1 at the beginning
finally:
# Always save data to database, even if all values are -1
cursor.execute('''
INSERT INTO data_NPM_5channels (timestamp,PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5) VALUES (?,?,?,?,?,?)'''
, (rtc_time_str, channel_1, channel_2, channel_3, channel_4, channel_5))
@@ -173,5 +194,4 @@ INSERT INTO data_NPM (timestamp,PM1, PM25, PM10, temp_npm, hum_npm) VALUES (?,?,
# Commit and close the connection
conn.commit()
conn.close()

View File

@@ -101,7 +101,7 @@ function getNPM_values(port){
$("#loading_"+port).hide();
// Create an array of the desired keys
const keysToShow = ["PM1", "PM25", "PM10"];
const keysToShow = ["PM1", "PM25", "PM10","message"];
// Error messages mapping
const errorMessages = {
"notReady": "Sensor is not ready",
@@ -307,8 +307,8 @@ error: function(xhr, status, error) {
const container = document.getElementById('card-container'); // Conteneur des cartes
//creates NPM card
if (response["NPM/get_data_modbus_v3.py"]) {
//creates NPM card (by default)
const cardHTML = `
<div class="col-sm-3">
<div class="card">
@@ -329,7 +329,7 @@ error: function(xhr, status, error) {
</div>`;
container.innerHTML += cardHTML; // Add the I2C card if condition is met
}
//creates i2c BME280 card
if (response["BME280/get_data_v2.py"]) {