This commit is contained in:
PaulVua
2025-02-18 12:05:43 +01:00
parent 9774215e7c
commit 6622be14ad
8 changed files with 390 additions and 49 deletions

View File

@@ -0,0 +1,172 @@
'''
Loop to run every minutes
* * * * * /usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_loop.py ttyAMA5
saves data (avaerage) to a json file /var/www/nebuleair_pro_4g/NPM/data/data.json
saves data (all) to a sqlite database
first time running the script?
sudo mkdir /var/www/nebuleair_pro_4g/NPM/data
sudo touch /var/www/nebuleair_pro_4g/NPM/data/data.json
'''
import serial
import sys
import crcmod
import time
import json
import os
import sqlite3
import smbus2 # For RTC DS3231
from datetime import datetime
# Ensure a port argument is provided
if len(sys.argv) < 2:
print("Usage: python3 get_data_modbus.py <serial_port>")
sys.exit(1)
port = '/dev/' + sys.argv[1]
# Initialize serial communication
try:
ser = serial.Serial(
port=port,
baudrate=115200,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=0.5
)
except Exception as e:
print(f"Error opening serial port {port}: {e}")
sys.exit(1)
# Define Modbus CRC-16 function
crc16 = crcmod.predefined.mkPredefinedCrcFun('modbus')
# Request frame without CRC
data = b'\x01\x03\x00\x80\x00\x0A'
# Calculate CRC
crc = crc16(data)
crc_low = crc & 0xFF
crc_high = (crc >> 8) & 0xFF
# Append CRC to the frame
request = data + bytes([crc_low, crc_high])
# Log request frame
print(f"Request frame: {request.hex()}")
# Initialize SQLite database
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
# RTC Module (DS3231) Setup
RTC_I2C_ADDR = 0x68 # DS3231 I2C Address
bus = smbus2.SMBus(1)
def bcd_to_dec(bcd):
return (bcd // 16 * 10) + (bcd % 16)
def get_rtc_time():
"""Reads time from RTC module (DS3231)"""
try:
data = bus.read_i2c_block_data(RTC_I2C_ADDR, 0x00, 7)
seconds = bcd_to_dec(data[0] & 0x7F)
minutes = bcd_to_dec(data[1])
hours = bcd_to_dec(data[2] & 0x3F)
day = bcd_to_dec(data[4])
month = bcd_to_dec(data[5])
year = bcd_to_dec(data[6]) + 2000
return datetime(year, month, day, hours, minutes, seconds).strftime("%Y-%m-%d %H:%M:%S")
except Exception as e:
print(f"RTC Read Error: {e}")
return "N/A"
# Initialize storage for averaging
num_samples = 6
channel_sums = [0] * 5 # 5 channels
#do not start immediately to prevent multiple write/read over NPM serial port
time.sleep(3)
# Loop 6 times to collect data every 10 seconds
for i in range(num_samples):
print(f"\nIteration {i+1}/{num_samples}")
ser.write(request)
rtc_timestamp = get_rtc_time()
try:
byte_data = ser.readline()
formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data)
print(f"Raw Response: {formatted}")
if len(byte_data) < 23:
print("Incomplete response, skipping this sample.")
time.sleep(9)
continue
# Extract and process the 5 channels
channels = []
for j in range(5):
lsw = int.from_bytes(byte_data[3 + j*4 : 5 + j*4], byteorder='little')
msw = int.from_bytes(byte_data[5 + j*4 : 7 + j*4], byteorder='little')
raw_value = (msw << 16) | lsw
channels.append(raw_value)
# Accumulate sum for each channel
for j in range(5):
channel_sums[j] += channels[j]
# Print collected values
print(f"Timestamp (RTC): {rtc_timestamp}")
for j in range(5):
print(f"Channel {j+1}: {channels[j]}")
# Save the individual reading to the database
cursor.execute('''
INSERT INTO data (timestamp, PM_ch1, PM_ch2, PM_ch3, PM_ch4, PM_ch5)
VALUES (?, ?, ?, ?, ?, ?)
''', (rtc_timestamp, channels[0], channels[1], channels[2], channels[3], channels[4]))
conn.commit()
except Exception as e:
print(f"Error reading data: {e}")
# Wait 10 seconds before next reading
time.sleep(10)
# Compute the average values
channel_means = [int(s / num_samples) for s in channel_sums]
# Create JSON structure
data_json = {
"channel_1": channel_means[0],
"channel_2": channel_means[1],
"channel_3": channel_means[2],
"channel_4": channel_means[3],
"channel_5": channel_means[4]
}
# Print final JSON data
print("\nFinal JSON Data (Averaged over 6 readings):")
print(json.dumps(data_json, indent=4))
# Define JSON file path
output_file = "/var/www/nebuleair_pro_4g/NPM/data/data.json"
# Write results to a JSON file
try:
with open(output_file, "w") as f:
json.dump(data_json, f, indent=4)
print(f"\nAveraged JSON data saved to {output_file}")
except Exception as e:
print(f"Error writing to file: {e}")
# Close serial connection
ser.close()

126
NPM/old/test_modbus.py Normal file
View File

@@ -0,0 +1,126 @@
'''
_ _ ____ __ __
| \ | | _ \| \/ |
| \| | |_) | |\/| |
| |\ | __/| | | |
|_| \_|_| |_| |_|
Script to get NPM data via Modbus
need parameter: port
/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/old/test_modbus.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
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
# 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
config_file = '/var/www/nebuleair_pro_4g/config.json'
config = load_config(config_file)
npm_solo_port = config.get('NPM_solo_port', '') #port du NPM solo
ser = serial.Serial(
port=npm_solo_port,
baudrate=115200,
parity=serial.PARITY_EVEN,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout = 0.5
)
# Define Modbus CRC-16 function
crc16 = crcmod.predefined.mkPredefinedCrcFun('modbus')
# Request frame without CRC
data = b'\x01\x03\x00\x44\x00\x06'
# Calculate CRC
crc = crc16(data)
crc_low = crc & 0xFF
crc_high = (crc >> 8) & 0xFF
# Append CRC to the frame
request = data + bytes([crc_low, crc_high])
#print(f"Request frame: {request.hex()}")
ser.write(request)
while True:
try:
byte_data = ser.readline()
formatted = ''.join(f'\\x{byte:02x}' for byte in byte_data)
print(formatted)
if len(byte_data) < 14:
print(f"Error: Received {len(byte_data)} bytes, expected 14!")
continue
#10 secs concentration
lsw_pm1 = int.from_bytes(byte_data[3:5], byteorder='big')
msw_pm1 = int.from_bytes(byte_data[5:7], byteorder='big')
raw_value_pm1 = (msw_pm1 << 16) | lsw_pm1
raw_value_pm1 = raw_value_pm1 / 1000
lsw_pm25 = int.from_bytes(byte_data[7:9], byteorder='big')
msw_pm25 = int.from_bytes(byte_data[9:11], byteorder='big')
raw_value_pm25 = (msw_pm25 << 16) | lsw_pm25
raw_value_pm25 = raw_value_pm25 / 1000
lsw_pm10 = int.from_bytes(byte_data[11:13], byteorder='big')
msw_pm10 = int.from_bytes(byte_data[13:15], byteorder='big')
raw_value_pm10 = (msw_pm10 << 16) | lsw_pm10
raw_value_pm10 = raw_value_pm10 / 1000
print("1 min")
print(f"PM1: {raw_value_pm1}")
print(f"PM2.5: {raw_value_pm25}")
print(f"PM10: {raw_value_pm10}")
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()
conn.close()