update
This commit is contained in:
320
loop/SARA_send_data_v2.py
Normal file
320
loop/SARA_send_data_v2.py
Normal file
@@ -0,0 +1,320 @@
|
||||
"""
|
||||
____ _ ____ _ ____ _ ____ _
|
||||
/ ___| / \ | _ \ / \ / ___| ___ _ __ __| | | _ \ __ _| |_ __ _
|
||||
\___ \ / _ \ | |_) | / _ \ \___ \ / _ \ '_ \ / _` | | | | |/ _` | __/ _` |
|
||||
___) / ___ \| _ < / ___ \ ___) | __/ | | | (_| | | |_| | (_| | || (_| |
|
||||
|____/_/ \_\_| \_\/_/ \_\ |____/ \___|_| |_|\__,_| |____/ \__,_|\__\__,_|
|
||||
|
||||
Main loop to gather data from sensor inside SQLite database:
|
||||
|
||||
* NPM
|
||||
* Envea
|
||||
* I2C BME280
|
||||
* Noise sensor
|
||||
|
||||
and send it to AirCarto servers via SARA R4 HTTP post requests
|
||||
also send the timestamp (already stored inside the DB) !
|
||||
|
||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/loop/SARA_send_data_v2.py
|
||||
|
||||
|
||||
ATTENTION:
|
||||
# This script is triggered every minutes by /var/www/nebuleair_pro_4g/master.py (as a service)
|
||||
|
||||
CSV PAYLOAD (AirCarto Servers)
|
||||
Endpoint:
|
||||
data.nebuleair.fr
|
||||
/pro_4G/data.php?sensor_id={device_id}×tamp={rtc_module_time}
|
||||
|
||||
ATTENTION : do not change order !
|
||||
CSV size: 18
|
||||
{PM1},{PM25},{PM10},{temp},{hum},{press},{avg_noise},{max_noise},{min_noise},{envea_no2},{envea_h2s},{envea_o3},{4g_signal_quality}
|
||||
0 -> PM1 (μg/m3)
|
||||
1 -> PM25 (μg/m3)
|
||||
2 -> PM10 (μg/m3)
|
||||
3 -> temp
|
||||
4 -> hum
|
||||
5 -> press
|
||||
6 -> avg_noise
|
||||
7 -> max_noise
|
||||
8 -> min_noise
|
||||
9 -> envea_no2
|
||||
10 -> envea_h2s
|
||||
11 -> envea_o3
|
||||
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)
|
||||
15 -> PM 1.0μm to 2.5μm quantity (Nb/L)
|
||||
16 -> PM 2.5μm to 5.0μm quantity (Nb/L)
|
||||
17 -> PM 5.0μm to 10μm quantity (Nb/L)
|
||||
|
||||
JSON PAYLOAD (Micro-Spot Servers)
|
||||
Same as NebuleAir wifi
|
||||
Endpoint:
|
||||
api-prod.uspot.probesys.net
|
||||
nebuleair?token=2AFF6dQk68daFZ
|
||||
port 443
|
||||
|
||||
{"nebuleairid": "82D25549434",
|
||||
"software_version": "ModuleAirV2-V1-042022",
|
||||
"sensordatavalues":
|
||||
[
|
||||
{"value_type":"NPM_P0","value":"1.54"},
|
||||
{"value_type":"NPM_P1","value":"1.54"},
|
||||
{"value_type":"NPM_P2","value":"1.54"},
|
||||
{"value_type":"NPM_N1","value":"0.02"},
|
||||
{"value_type":"NPM_N10","value":"0.02"},
|
||||
{"value_type":"NPM_N25","value":"0.02"},
|
||||
{"value_type":"MHZ16_CO2","value":"793.00"},
|
||||
{"value_type":"SGP40_VOC","value":"29915.00"},
|
||||
{"value_type":"samples","value":"134400"},
|
||||
{"value_type":"min_micro","value":"137"},
|
||||
{"value_type":"max_micro","value":"155030"},
|
||||
{"value_type":"interval","value":"145000"},
|
||||
{"value_type":"signal","value":"-80"},
|
||||
{"value_type":"latitude","value":"43.2964"},
|
||||
{"value_type":"longitude","value":"5.36978"},
|
||||
{"value_type":"state_npm","value":"State: 00000000"},
|
||||
{"value_type":"BME280_temperature","value":"28.47"},
|
||||
{"value_type":"BME280_humidity","value":"28.47"},
|
||||
{"value_type":"BME280_pressure","value":"28.47"},
|
||||
{"value_type":"CAIRSENS_NO2","value":"54"},
|
||||
{"value_type":"CAIRSENS_H2S","value":"54"},
|
||||
{"value_type":"CAIRSENS_O3","value":"54"}
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
import board
|
||||
import json
|
||||
import serial
|
||||
import time
|
||||
import busio
|
||||
import re
|
||||
import os
|
||||
import traceback
|
||||
import sys
|
||||
import sqlite3
|
||||
import RPi.GPIO as GPIO
|
||||
from threading import Thread
|
||||
|
||||
# Record the start time of the script
|
||||
start_time_script = time.time()
|
||||
|
||||
# Check system uptime
|
||||
with open('/proc/uptime', 'r') as f:
|
||||
uptime_seconds = float(f.readline().split()[0])
|
||||
|
||||
# Skip execution if uptime is less than 2 minutes (120 seconds)
|
||||
if uptime_seconds < 120:
|
||||
print(f"System just booted ({uptime_seconds:.2f} seconds uptime), skipping execution.")
|
||||
sys.exit()
|
||||
|
||||
#Payload CSV to be sent to data.nebuleair.fr
|
||||
payload_csv = [None] * 20
|
||||
#Payload JSON to be sent to uSpot
|
||||
payload_json = {
|
||||
"nebuleairid": "XXX",
|
||||
"software_version": "ModuleAirV2-V1-042022",
|
||||
"sensordatavalues": [] # Empty list to start with
|
||||
}
|
||||
|
||||
# SARA R4 UHTTPC profile IDs
|
||||
aircarto_profile_id = 0
|
||||
uSpot_profile_id = 1
|
||||
|
||||
# database connection
|
||||
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
def blink_led(pin, blink_count, delay=1):
|
||||
"""
|
||||
Blink an LED on a specified GPIO pin.
|
||||
|
||||
Args:
|
||||
pin (int): GPIO pin number (BCM mode) to which the LED is connected.
|
||||
blink_count (int): Number of times the LED should blink.
|
||||
delay (float): Time in seconds for the LED to stay ON or OFF (default is 1 second).
|
||||
"""
|
||||
# GPIO setup
|
||||
GPIO.setwarnings(False)
|
||||
GPIO.setmode(GPIO.BCM) # Use BCM numbering
|
||||
GPIO.setup(pin, GPIO.OUT) # Set the specified pin as an output
|
||||
|
||||
try:
|
||||
for _ in range(blink_count):
|
||||
GPIO.output(pin, GPIO.HIGH) # Turn the LED on
|
||||
#print(f"LED on GPIO {pin} is ON")
|
||||
time.sleep(delay) # Wait for the specified delay
|
||||
GPIO.output(pin, GPIO.LOW) # Turn the LED off
|
||||
#print(f"LED on GPIO {pin} is OFF")
|
||||
time.sleep(delay) # Wait for the specified delay
|
||||
finally:
|
||||
GPIO.cleanup(pin) # Clean up the specific pin to reset its state
|
||||
print(f"GPIO {pin} cleaned up")
|
||||
|
||||
#get data from config
|
||||
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 {}
|
||||
|
||||
#Fonction pour mettre à jour le JSON de configuration
|
||||
def update_json_key(file_path, key, value):
|
||||
"""
|
||||
Updates a specific key in a JSON file with a new value.
|
||||
|
||||
:param file_path: Path to the JSON file.
|
||||
:param key: The key to update in the JSON file.
|
||||
:param value: The new value to assign to the key.
|
||||
"""
|
||||
try:
|
||||
# Load the existing data
|
||||
with open(file_path, "r") as file:
|
||||
data = json.load(file)
|
||||
|
||||
# Check if the key exists in the JSON file
|
||||
if key in data:
|
||||
data[key] = value # Update the key with the new value
|
||||
else:
|
||||
print(f"Key '{key}' not found in the JSON file.")
|
||||
return
|
||||
|
||||
# Write the updated data back to the file
|
||||
with open(file_path, "w") as file:
|
||||
json.dump(data, file, indent=2) # Use indent for pretty printing
|
||||
|
||||
print(f"updating '{key}' to '{value}'.")
|
||||
except Exception as e:
|
||||
print(f"Error updating the JSON file: {e}")
|
||||
|
||||
# Define the config file path
|
||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||
|
||||
# Load the configuration data
|
||||
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
|
||||
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', '')
|
||||
|
||||
#update device id in the payload json
|
||||
payload_json["nebuleairid"] = device_id
|
||||
|
||||
ser_sara = serial.Serial(
|
||||
port='/dev/ttyAMA2',
|
||||
baudrate=baudrate, #115200 ou 9600
|
||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||
stopbits=serial.STOPBITS_ONE,
|
||||
bytesize=serial.EIGHTBITS,
|
||||
timeout = 2
|
||||
)
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_line=None, debug=True):
|
||||
'''
|
||||
Fonction très importante !!!
|
||||
'''
|
||||
response = bytearray()
|
||||
serial_connection.timeout = timeout
|
||||
end_time = time.time() + end_of_response_timeout
|
||||
start_time = time.time()
|
||||
|
||||
while True:
|
||||
elapsed_time = time.time() - start_time # Time since function start
|
||||
if serial_connection.in_waiting > 0:
|
||||
data = serial_connection.read(serial_connection.in_waiting)
|
||||
response.extend(data)
|
||||
end_time = time.time() + end_of_response_timeout # Reset timeout on new data
|
||||
|
||||
# Decode and check for the specific line
|
||||
if wait_for_line:
|
||||
decoded_response = response.decode('utf-8', errors='replace')
|
||||
if wait_for_line in decoded_response:
|
||||
if debug: print(f"[DEBUG] 🔎Found target line: {wait_for_line}")
|
||||
break
|
||||
elif time.time() > end_time:
|
||||
if debug: print(f"[DEBUG] Timeout reached. No more data received.")
|
||||
break
|
||||
time.sleep(0.1) # Short sleep to prevent busy waiting
|
||||
# Final response and debug output
|
||||
total_elapsed_time = time.time() - start_time
|
||||
if debug: print(f"[DEBUG] ⏱️ elapsed time: {total_elapsed_time:.2f}s. ⏱️")
|
||||
# Check if the elapsed time exceeded 10 seconds
|
||||
if total_elapsed_time > 10 and debug:
|
||||
print(f"[ALERT] 🚨 The operation took too long🚨")
|
||||
print(f'<span style="color: red;font-weight: bold;">[ALERT] ⚠️{total_elapsed_time:.2f}s⚠️</span>')
|
||||
|
||||
return response.decode('utf-8', errors='replace')
|
||||
|
||||
try:
|
||||
print('<h3>START LOOP</h3>')
|
||||
print("Getting NPM values")
|
||||
# Retrieve the last sensor readings
|
||||
cursor.execute("SELECT * FROM data ORDER BY timestamp DESC LIMIT 1")
|
||||
last_row = cursor.fetchone()
|
||||
# Display the result
|
||||
if last_row:
|
||||
pm1_value = last_row[1] # Adjust the index based on the column order in your table
|
||||
print("Last available row:", last_row)
|
||||
else:
|
||||
print("No data available in the database.")
|
||||
|
||||
|
||||
print("Verify SARA R4 connection")
|
||||
|
||||
# Getting the LTE Signal
|
||||
print("-> Getting LTE signal <-")
|
||||
ser_sara.write(b'AT+CSQ\r')
|
||||
response2 = read_complete_response(ser_sara, wait_for_line="OK")
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response2)
|
||||
print("</p>")
|
||||
match = re.search(r'\+CSQ:\s*(\d+),', response2)
|
||||
if match:
|
||||
signal_quality = int(match.group(1))
|
||||
payload_csv[12]=signal_quality
|
||||
time.sleep(0.1)
|
||||
|
||||
# On vérifie si le signal n'est pas à 99 pour déconnexion
|
||||
# si c'est le cas on essaie de se reconnecter
|
||||
if signal_quality == 99:
|
||||
print('<span style="color: red;font-weight: bold;">⚠️ATTENTION: Signal Quality indicates no signal (99)⚠️</span>')
|
||||
print("TRY TO RECONNECT:")
|
||||
command = f'AT+COPS=1,2,"{selected_networkID}"\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(responseReconnect)
|
||||
print("</p>")
|
||||
|
||||
print('🛑STOP LOOP🛑')
|
||||
print("<hr>")
|
||||
|
||||
#on arrete le script pas besoin de continuer
|
||||
sys.exit()
|
||||
else:
|
||||
print("Signal Quality:", signal_quality)
|
||||
|
||||
|
||||
'''
|
||||
SEND TO AIRCARTO
|
||||
'''
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Calculate and print the elapsed time
|
||||
elapsed_time = time.time() - start_time_script
|
||||
print(f"Elapsed time: {elapsed_time:.2f} seconds")
|
||||
print("<hr>")
|
||||
|
||||
except Exception as e:
|
||||
print("An error occurred:", e)
|
||||
traceback.print_exc() # This prints the full traceback
|
||||
Reference in New Issue
Block a user