update
This commit is contained in:
@@ -7,14 +7,13 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
|
|||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install git gh apache2 php php-sqlite3 python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y
|
sudo apt install git gh apache2 php php-sqlite3 python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y
|
||||||
sudo pip3 install pyserial requests sensirion-shdlc-sfa3x RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil --break-system-packages
|
sudo pip3 install pyserial requests sensirion-shdlc-sfa3x RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil --break-system-packages
|
||||||
sudo gh auth login
|
sudo git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g
|
||||||
git config --global user.email "paulvuarambon@gmail.com"
|
|
||||||
git config --global user.name "PaulVua"
|
|
||||||
sudo gh repo clone aircarto/moduleair_pro_4g /var/www/moduleair_pro_4g
|
|
||||||
sudo mkdir /var/www/moduleair_pro_4g/logs
|
sudo mkdir /var/www/moduleair_pro_4g/logs
|
||||||
sudo touch /var/www/moduleair_pro_4g/logs/app.log /var/www/moduleair_pro_4g/logs/loop.log /var/www/moduleair_pro_4g/matrix/input_NPM.txt /var/www/moduleair_pro_4g/matrix/input_MHZ16.txt /var/www/moduleair_pro_4g/wifi_list.csv
|
sudo touch /var/www/moduleair_pro_4g/logs/app.log /var/www/moduleair_pro_4g/logs/loop.log /var/www/moduleair_pro_4g/matrix/input_NPM.txt /var/www/moduleair_pro_4g/matrix/input_MHZ16.txt /var/www/moduleair_pro_4g/wifi_list.csv
|
||||||
sudo cp /var/www/moduleair_pro_4g/config.json.dist /var/www/moduleair_pro_4g/config.json
|
sudo cp /var/www/moduleair_pro_4g/config.json.dist /var/www/moduleair_pro_4g/config.json
|
||||||
sudo chmod -R 777 /var/www/moduleair_pro_4g/
|
sudo chmod -R 777 /var/www/moduleair_pro_4g/
|
||||||
|
git config core.fileMode false
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Apache
|
## Apache
|
||||||
|
|||||||
@@ -17,52 +17,96 @@ import time
|
|||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
#get data from config
|
# database connection
|
||||||
def load_config(config_file):
|
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
#get config data from SQLite table
|
||||||
|
def load_config_sqlite():
|
||||||
|
"""
|
||||||
|
Load configuration data from SQLite config table
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Configuration data with proper type conversion
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
with open(config_file, 'r') as file:
|
|
||||||
config_data = json.load(file)
|
# Query the config table
|
||||||
|
cursor.execute("SELECT key, value, type FROM config_table")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
# Create config dictionary
|
||||||
|
config_data = {}
|
||||||
|
for key, value, type_name in rows:
|
||||||
|
# Convert value based on its type
|
||||||
|
if type_name == 'bool':
|
||||||
|
config_data[key] = value == '1' or value == 'true'
|
||||||
|
elif type_name == 'int':
|
||||||
|
config_data[key] = int(value)
|
||||||
|
elif type_name == 'float':
|
||||||
|
config_data[key] = float(value)
|
||||||
|
else:
|
||||||
|
config_data[key] = value
|
||||||
|
|
||||||
return config_data
|
return config_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading config file: {e}")
|
print(f"Error loading config from SQLite: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
#Fonction pour mettre à jour le JSON de configuration
|
def update_sqlite_config(key, value):
|
||||||
def update_json_key(file_path, key, value):
|
|
||||||
"""
|
"""
|
||||||
Updates a specific key in a JSON file with a new value.
|
Updates a specific key in the SQLite config_table with a new value.
|
||||||
|
|
||||||
:param file_path: Path to the JSON file.
|
:param key: The key to update in the config_table.
|
||||||
:param key: The key to update in the JSON file.
|
|
||||||
:param value: The new value to assign to the key.
|
:param value: The new value to assign to the key.
|
||||||
"""
|
"""
|
||||||
try:
|
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
|
# Check if the key exists and get its type
|
||||||
if key in data:
|
cursor.execute("SELECT type FROM config_table WHERE key = ?", (key,))
|
||||||
data[key] = value # Update the key with the new value
|
result = cursor.fetchone()
|
||||||
else:
|
|
||||||
print(f"Key '{key}' not found in the JSON file.")
|
if result is None:
|
||||||
|
print(f"Key '{key}' not found in the config_table.")
|
||||||
|
conn.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Write the updated data back to the file
|
# Get the type of the value from the database
|
||||||
with open(file_path, "w") as file:
|
value_type = result[0]
|
||||||
json.dump(data, file, indent=2) # Use indent for pretty printing
|
|
||||||
|
|
||||||
print(f"💾 updating '{key}' to '{value}'.")
|
# Convert the value to the appropriate string representation based on its type
|
||||||
|
if value_type == 'bool':
|
||||||
|
# Convert Python boolean or string 'true'/'false' to '1'/'0'
|
||||||
|
if isinstance(value, bool):
|
||||||
|
str_value = '1' if value else '0'
|
||||||
|
else:
|
||||||
|
str_value = '1' if str(value).lower() in ('true', '1', 'yes', 'y') else '0'
|
||||||
|
elif value_type == 'int':
|
||||||
|
str_value = str(int(value))
|
||||||
|
elif value_type == 'float':
|
||||||
|
str_value = str(float(value))
|
||||||
|
else:
|
||||||
|
str_value = str(value)
|
||||||
|
|
||||||
|
# Update the value in the database
|
||||||
|
cursor.execute("UPDATE config_table SET value = ? WHERE key = ?", (str_value, key))
|
||||||
|
|
||||||
|
# Commit the changes and close the connection
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
print(f"💾 Updated '{key}' to '{value}' in database.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error updating the JSON file: {e}")
|
print(f"Error updating the SQLite database: {e}")
|
||||||
|
|
||||||
# Define the config file path
|
#Load config
|
||||||
config_file = '/var/www/moduleair_pro_4g/config.json'
|
config = load_config_sqlite()
|
||||||
# Load the configuration data
|
#config
|
||||||
config = load_config(config_file)
|
|
||||||
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
|
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
|
||||||
device_id = config.get('deviceID', '').upper() #device ID en maj
|
device_id = config.get('deviceID', '').upper() #device ID en maj
|
||||||
|
sara_r5_DPD_setup = False
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
ser_sara = serial.Serial(
|
||||||
port='/dev/ttyAMA2',
|
port='/dev/ttyAMA2',
|
||||||
@@ -117,21 +161,46 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
|
|
||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print('<h3>Start reboot python script</h3>')
|
print('<h3>Start reboot python script</h3>')
|
||||||
|
|
||||||
#check modem status
|
#check modem status
|
||||||
|
#Attention:
|
||||||
|
# SARA R4 response: Manufacturer: u-blox Model: SARA-R410M-02B
|
||||||
|
# SArA R5 response: SARA-R500S-01B-00
|
||||||
print("⚙️Check SARA Status")
|
print("⚙️Check SARA Status")
|
||||||
command = f'ATI\r'
|
command = f'ATI\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
response_SARA_ATI = read_complete_response(ser_sara, wait_for_lines=["IMEI"])
|
response_SARA_ATI = read_complete_response(ser_sara, wait_for_lines=["IMEI"])
|
||||||
print(response_SARA_ATI)
|
print(response_SARA_ATI)
|
||||||
match = re.search(r"Model:\s*(.+)", response_SARA_ATI)
|
|
||||||
model = match.group(1).strip() if match else "Unknown" # Strip unwanted characters
|
# Check for SARA model with more robust regex
|
||||||
print(f" Model: {model}")
|
model = "Unknown"
|
||||||
update_json_key(config_file, "modem_version", model)
|
if "SARA-R410M" in response_SARA_ATI:
|
||||||
|
model = "SARA-R410M"
|
||||||
|
print("📱 Detected SARA R4 modem")
|
||||||
|
elif "SARA-R500" in response_SARA_ATI:
|
||||||
|
model = "SARA-R500"
|
||||||
|
print("📱 Detected SARA R5 modem")
|
||||||
|
sara_r5_DPD_setup = True
|
||||||
|
else:
|
||||||
|
# Fallback to regex match if direct string match fails
|
||||||
|
match = re.search(r"Model:\s*([A-Za-z0-9\-]+)", response_SARA_ATI)
|
||||||
|
if match:
|
||||||
|
model = match.group(1).strip()
|
||||||
|
else:
|
||||||
|
model = "Unknown"
|
||||||
|
print("⚠️ Could not identify modem model")
|
||||||
|
|
||||||
|
print(f"🔍 Model: {model}")
|
||||||
|
update_sqlite_config("modem_version", model)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
AIRCARTO
|
||||||
|
'''
|
||||||
|
|
||||||
# 1. Set AIRCARTO URL
|
# 1. Set AIRCARTO URL
|
||||||
print('➡️Set aircarto URL')
|
print('➡️Set aircarto URL')
|
||||||
@@ -143,6 +212,10 @@ try:
|
|||||||
print(response_SARA_1)
|
print(response_SARA_1)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
'''
|
||||||
|
uSpot
|
||||||
|
'''
|
||||||
|
|
||||||
#2. Set uSpot URL
|
#2. Set uSpot URL
|
||||||
print('➡️Set uSpot URL')
|
print('➡️Set uSpot URL')
|
||||||
uSpot_profile_id = 1
|
uSpot_profile_id = 1
|
||||||
@@ -179,9 +252,9 @@ try:
|
|||||||
else:
|
else:
|
||||||
print("❌ Failed to extract coordinates.")
|
print("❌ Failed to extract coordinates.")
|
||||||
|
|
||||||
#update config.json
|
#update sqlite table
|
||||||
update_json_key(config_file, "latitude_raw", float(latitude))
|
update_sqlite_config("latitude_raw", float(latitude))
|
||||||
update_json_key(config_file, "longitude_raw", float(longitude))
|
update_sqlite_config("longitude_raw", float(longitude))
|
||||||
|
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
command = parameter[1] # ex: AT+CCID?
|
command = parameter[1] # ex: AT+CCID?
|
||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -24,11 +24,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
networkID = parameter[1] # ex: 20801
|
networkID = parameter[1] # ex: 20801
|
||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -17,11 +17,9 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||||
message = parameter[1] # ex: Hello
|
message = parameter[1] # ex: Hello
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -16,11 +16,9 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||||
message = parameter[1] # ex: Hello
|
message = parameter[1] # ex: Hello
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -18,11 +18,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
endpoint = parameter[1] # ex: /pro_4G/notif_message.php
|
endpoint = parameter[1] # ex: /pro_4G/notif_message.php
|
||||||
profile_id = parameter[2]
|
profile_id = parameter[2]
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -22,12 +22,9 @@ apn_address = parameter[1] # ex: data.mono
|
|||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -27,12 +27,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
url = parameter[1] # ex: data.mobileair.fr
|
url = parameter[1] # ex: data.mobileair.fr
|
||||||
profile_id = parameter[2] #ex: 0
|
profile_id = parameter[2] #ex: 0
|
||||||
|
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -46,11 +46,9 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
|
|
||||||
return response.decode('utf-8', errors='replace')
|
return response.decode('utf-8', errors='replace')
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser_sara = serial.Serial(
|
ser_sara = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -17,12 +17,9 @@ parameter = sys.argv[1:] # Exclude the script name
|
|||||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||||
message = parameter[1] # ex: Hello
|
message = parameter[1] # ex: Hello
|
||||||
|
|
||||||
|
|
||||||
baudrate = 9600
|
|
||||||
|
|
||||||
ser = serial.Serial(
|
ser = serial.Serial(
|
||||||
port=port, #USB0 or ttyS0
|
port=port, #USB0 or ttyS0
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=115200, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
|
|||||||
@@ -156,60 +156,88 @@ def blink_led(pin, blink_count, delay=1):
|
|||||||
GPIO.cleanup(pin) # Clean up the specific pin to reset its state
|
GPIO.cleanup(pin) # Clean up the specific pin to reset its state
|
||||||
print(f"GPIO {pin} cleaned up")
|
print(f"GPIO {pin} cleaned up")
|
||||||
|
|
||||||
#get data from config
|
#get config data from SQLite table
|
||||||
def load_config(config_file):
|
def load_config_sqlite():
|
||||||
|
"""
|
||||||
|
Load configuration data from SQLite config table
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: Configuration data with proper type conversion
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
with open(config_file, 'r') as file:
|
|
||||||
config_data = json.load(file)
|
# Query the config table
|
||||||
|
cursor.execute("SELECT key, value, type FROM config_table")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
# Create config dictionary
|
||||||
|
config_data = {}
|
||||||
|
for key, value, type_name in rows:
|
||||||
|
# Convert value based on its type
|
||||||
|
if type_name == 'bool':
|
||||||
|
config_data[key] = value == '1' or value == 'true'
|
||||||
|
elif type_name == 'int':
|
||||||
|
config_data[key] = int(value)
|
||||||
|
elif type_name == 'float':
|
||||||
|
config_data[key] = float(value)
|
||||||
|
else:
|
||||||
|
config_data[key] = value
|
||||||
|
|
||||||
return config_data
|
return config_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error loading config file: {e}")
|
print(f"Error loading config from SQLite: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
#Fonction pour mettre à jour le JSON de configuration
|
def load_config_scripts_sqlite():
|
||||||
def update_json_key(file_path, key, value):
|
|
||||||
"""
|
"""
|
||||||
Updates a specific key in a JSON file with a new value.
|
Load script configuration data from SQLite config_scripts_table
|
||||||
|
|
||||||
:param file_path: Path to the JSON file.
|
Returns:
|
||||||
:param key: The key to update in the JSON file.
|
dict: Script paths as keys and enabled status as boolean values
|
||||||
:param value: The new value to assign to the key.
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Load the existing data
|
# Query the config_scripts_table
|
||||||
with open(file_path, "r") as file:
|
cursor.execute("SELECT script_path, enabled FROM config_scripts_table")
|
||||||
data = json.load(file)
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
# Check if the key exists in the JSON file
|
# Create config dictionary with script paths as keys and enabled status as boolean values
|
||||||
if key in data:
|
scripts_config = {}
|
||||||
data[key] = value # Update the key with the new value
|
for script_path, enabled in rows:
|
||||||
else:
|
# Convert integer enabled value (0/1) to boolean
|
||||||
print(f"Key '{key}' not found in the JSON file.")
|
scripts_config[script_path] = bool(enabled)
|
||||||
return
|
|
||||||
|
|
||||||
# Write the updated data back to the file
|
return scripts_config
|
||||||
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:
|
except Exception as e:
|
||||||
print(f"Error updating the JSON file: {e}")
|
print(f"Error loading scripts config from SQLite: {e}")
|
||||||
|
return {}
|
||||||
|
|
||||||
# Define the config file path
|
# Define the config file path
|
||||||
config_file = '/var/www/moduleair_pro_4g/config.json'
|
config_file = '/var/www/moduleair_pro_4g/config.json'
|
||||||
|
|
||||||
# Load the configuration data
|
#Load config
|
||||||
config = load_config(config_file)
|
config = load_config_sqlite()
|
||||||
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
|
#config
|
||||||
device_id = config.get('deviceID', '').upper() #device ID en maj
|
device_id = config.get('deviceID', 'unknown')
|
||||||
bme_280_config = config.get('BME280/get_data_v2.py', False) #présence du BME280
|
device_id = device_id.upper()
|
||||||
envea_cairsens= config.get('envea/read_value_v2.py', False)
|
modem_config_mode = config.get('modem_config_mode', False)
|
||||||
send_aircarto = config.get('send_aircarto', True) #envoi sur AirCarto (data.moduleair.fr)
|
device_latitude_raw = config.get('latitude_raw', 0)
|
||||||
|
device_longitude_raw = config.get('longitude_raw', 0)
|
||||||
|
modem_version=config.get('modem_version', "")
|
||||||
|
Sara_baudrate = config.get('SaraR4_baudrate', 115200)
|
||||||
|
npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM
|
||||||
|
selected_networkID = int(config.get('SARA_R4_neworkID', 0))
|
||||||
send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot ()
|
send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot ()
|
||||||
selected_networkID = config.get('SARA_R4_neworkID', '')
|
|
||||||
npm_5channel = config.get('NextPM_5channels', False) #5 canaux du NPM
|
|
||||||
|
|
||||||
modem_config_mode = config.get('modem_config_mode', False) #modem 4G en mode configuration
|
reset_uSpot_url = False
|
||||||
|
|
||||||
|
#config_scripts
|
||||||
|
config_scripts = load_config_scripts_sqlite()
|
||||||
|
bme_280_config = config_scripts.get('BME280/get_data_v2.py', False)
|
||||||
|
envea_cairsens= config_scripts.get('envea/read_value_v2.py', False)
|
||||||
|
co2_mhz19= config_scripts.get('MH-Z19/write_data.py', False)
|
||||||
|
sensirion_sfa30= config_scripts.get('sensirion/SFA30_read.py', False)
|
||||||
|
|
||||||
#update device id in the payload json
|
#update device id in the payload json
|
||||||
payload_json["moduleairid"] = device_id
|
payload_json["moduleairid"] = device_id
|
||||||
@@ -221,7 +249,7 @@ if modem_config_mode:
|
|||||||
|
|
||||||
ser_sara = serial.Serial(
|
ser_sara = serial.Serial(
|
||||||
port='/dev/ttyAMA2',
|
port='/dev/ttyAMA2',
|
||||||
baudrate=baudrate, #115200 ou 9600
|
baudrate=Sara_baudrate, #115200 ou 9600
|
||||||
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
|
||||||
stopbits=serial.STOPBITS_ONE,
|
stopbits=serial.STOPBITS_ONE,
|
||||||
bytesize=serial.EIGHTBITS,
|
bytesize=serial.EIGHTBITS,
|
||||||
@@ -272,6 +300,182 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
|
|
||||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
||||||
|
|
||||||
|
def extract_error_code(response):
|
||||||
|
"""
|
||||||
|
Extract just the error code from AT+UHTTPER response
|
||||||
|
"""
|
||||||
|
for line in response.split('\n'):
|
||||||
|
if '+UHTTPER' in line:
|
||||||
|
try:
|
||||||
|
# Split the line and get the third value (error code)
|
||||||
|
parts = line.split(':')[1].strip().split(',')
|
||||||
|
if len(parts) >= 3:
|
||||||
|
error_code = int(parts[2])
|
||||||
|
return error_code
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Return None if we couldn't find the error code
|
||||||
|
return None
|
||||||
|
|
||||||
|
def send_error_notification(device_id, error_type, additional_info=None):
|
||||||
|
"""
|
||||||
|
Send an error notification to the server when issues with the SARA module occur.
|
||||||
|
Will silently fail if there's no internet connection.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
-----------
|
||||||
|
device_id : str
|
||||||
|
The unique identifier of the device
|
||||||
|
error_type : str
|
||||||
|
Type of error encountered (e.g., 'serial_error', 'cme_error', 'http_error', 'timeout')
|
||||||
|
additional_info : str, optional
|
||||||
|
Any additional information about the error for logging purposes
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
--------
|
||||||
|
bool
|
||||||
|
True if notification was sent successfully, False otherwise
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create the alert URL with all relevant parameters
|
||||||
|
base_url = 'http://data.nebuleair.fr/pro_4G/alert.php'
|
||||||
|
alert_url = f'{base_url}?capteur_id={device_id}&error_type={error_type}'
|
||||||
|
|
||||||
|
# Add additional info if provided
|
||||||
|
if additional_info:
|
||||||
|
# Make sure to URL encode the additional info
|
||||||
|
from urllib.parse import quote
|
||||||
|
alert_url += f'&details={quote(str(additional_info))}'
|
||||||
|
|
||||||
|
# Try to send the notification, catch ALL exceptions
|
||||||
|
try:
|
||||||
|
response = requests.post(alert_url, timeout=3)
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"✅ Alert notification sent successfully")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"⚠️ Alert notification failed: Status code {response.status_code}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"⚠️ Alert notification couldn't be sent: {e}")
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id):
|
||||||
|
"""
|
||||||
|
Performs a complete modem restart sequence:
|
||||||
|
1. Reboots the modem using the appropriate command for its version
|
||||||
|
2. Waits for the modem to restart
|
||||||
|
3. Resets the HTTP profile
|
||||||
|
4. For SARA-R5, resets the PDP connection
|
||||||
|
|
||||||
|
Args:
|
||||||
|
modem_version (str): The modem version, e.g., 'SARA-R500' or 'SARA-R410'
|
||||||
|
aircarto_profile_id (int): The HTTP profile ID to reset
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if the complete sequence was successful, False otherwise
|
||||||
|
"""
|
||||||
|
print('<span style="color: orange;font-weight: bold;">🔄 Complete SARA reboot and reinitialize sequence 🔄</span>')
|
||||||
|
|
||||||
|
# Step 1: Reboot the modem - Integrated modem_software_reboot logic
|
||||||
|
print('<span style="color: orange;font-weight: bold;">🔄 Software SARA reboot! 🔄</span>')
|
||||||
|
|
||||||
|
# Use different commands based on modem version
|
||||||
|
if 'R5' in modem_version: # For SARA-R5 series
|
||||||
|
command = 'AT+CFUN=16\r' # Normal restart for R5
|
||||||
|
else: # For SARA-R4 series
|
||||||
|
command = 'AT+CFUN=15\r' # Factory reset for R4
|
||||||
|
|
||||||
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
response = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR"], debug=True)
|
||||||
|
|
||||||
|
print('<p class="text-danger-emphasis">')
|
||||||
|
print(response)
|
||||||
|
print("</p>", end="")
|
||||||
|
|
||||||
|
# Check if reboot command was acknowledged
|
||||||
|
reboot_success = response is not None and "OK" in response
|
||||||
|
if not reboot_success:
|
||||||
|
print("⚠️ Modem reboot command failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Step 2: Wait for the modem to restart (adjust time as needed)
|
||||||
|
print("Waiting for modem to restart...")
|
||||||
|
time.sleep(15) # 15 seconds should be enough for most modems to restart
|
||||||
|
|
||||||
|
# Step 3: Check if modem is responsive after reboot
|
||||||
|
print("Checking if modem is responsive...")
|
||||||
|
ser_sara.write(b'AT\r')
|
||||||
|
response_check = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
||||||
|
if response_check is None or "OK" not in response_check:
|
||||||
|
print("⚠️ Modem not responding after reboot")
|
||||||
|
return False
|
||||||
|
|
||||||
|
print("✅ Modem restarted successfully")
|
||||||
|
|
||||||
|
# Step 4: Reset the HTTP Profile
|
||||||
|
print('<span style="color: orange;font-weight: bold;">🔧 Resetting the HTTP Profile</span>')
|
||||||
|
command = f'AT+UHTTP={aircarto_profile_id},1,"data.nebuleair.fr"\r'
|
||||||
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
responseResetHTTP = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5,
|
||||||
|
wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
||||||
|
print('<p class="text-danger-emphasis">')
|
||||||
|
print(responseResetHTTP)
|
||||||
|
print("</p>", end="")
|
||||||
|
|
||||||
|
http_reset_success = responseResetHTTP is not None and "OK" in responseResetHTTP
|
||||||
|
if not http_reset_success:
|
||||||
|
print("⚠️ HTTP profile reset failed")
|
||||||
|
# Continue anyway, don't return False here
|
||||||
|
|
||||||
|
# Step 5: For SARA-R5, reset the PDP connection
|
||||||
|
pdp_reset_success = True
|
||||||
|
if modem_version == "SARA-R500":
|
||||||
|
print("⚠️ Need to reset PDP connection for SARA-R500")
|
||||||
|
|
||||||
|
# Activate PDP context 1
|
||||||
|
print('➡️ Activate PDP context 1')
|
||||||
|
command = f'AT+CGACT=1,1\r'
|
||||||
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
response_pdp1 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||||
|
print(response_pdp1, end="")
|
||||||
|
pdp_reset_success = pdp_reset_success and (response_pdp1 is not None and "OK" in response_pdp1)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Set the PDP type
|
||||||
|
print('➡️ Set the PDP type to IPv4 referring to the output of the +CGDCONT read command')
|
||||||
|
command = f'AT+UPSD=0,0,0\r'
|
||||||
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
response_pdp2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||||
|
print(response_pdp2, end="")
|
||||||
|
pdp_reset_success = pdp_reset_success and (response_pdp2 is not None and "OK" in response_pdp2)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Profile #0 is mapped on CID=1
|
||||||
|
print('➡️ Profile #0 is mapped on CID=1.')
|
||||||
|
command = f'AT+UPSD=0,100,1\r'
|
||||||
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
response_pdp3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||||
|
print(response_pdp3, end="")
|
||||||
|
pdp_reset_success = pdp_reset_success and (response_pdp3 is not None and "OK" in response_pdp3)
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Activate the PSD profile
|
||||||
|
print('➡️ Activate the PSD profile #0: the IPv4 address is already assigned by the network.')
|
||||||
|
command = f'AT+UPSDA=0,3\r'
|
||||||
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
response_pdp4 = read_complete_response(ser_sara, wait_for_lines=["OK", "+UUPSDA"])
|
||||||
|
print(response_pdp4, end="")
|
||||||
|
pdp_reset_success = pdp_reset_success and (response_pdp4 is not None and ("OK" in response_pdp4 or "+UUPSDA" in response_pdp4))
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if not pdp_reset_success:
|
||||||
|
print("⚠️ PDP connection reset had some issues")
|
||||||
|
|
||||||
|
# Return overall success
|
||||||
|
return http_reset_success and pdp_reset_success
|
||||||
|
|
||||||
try:
|
try:
|
||||||
'''
|
'''
|
||||||
_ ___ ___ ____
|
_ ___ ___ ____
|
||||||
@@ -288,16 +492,35 @@ try:
|
|||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
row = cursor.fetchone() # Get the first (and only) row
|
row = cursor.fetchone() # Get the first (and only) row
|
||||||
rtc_time_str = row[1] # '2025-02-07 12:30:45'
|
rtc_time_str = row[1] # '2025-02-07 12:30:45'
|
||||||
|
print(rtc_time_str)
|
||||||
|
|
||||||
|
if rtc_time_str == 'not connected':
|
||||||
|
print("⛔ Atttention RTC module not connected⛔")
|
||||||
|
rtc_status = "disconnected"
|
||||||
|
influx_timestamp="rtc_disconnected"
|
||||||
|
else :
|
||||||
# Convert to a datetime object
|
# Convert to a datetime object
|
||||||
dt_object = datetime.strptime(rtc_time_str, '%Y-%m-%d %H:%M:%S')
|
dt_object = datetime.strptime(rtc_time_str, '%Y-%m-%d %H:%M:%S')
|
||||||
|
# Check if timestamp is reset (year 2000)
|
||||||
|
if dt_object.year == 2000:
|
||||||
|
print("⛔ Attention: RTC has been reset to default date ⛔")
|
||||||
|
rtc_status = "reset"
|
||||||
|
else:
|
||||||
|
print("✅ RTC timestamp is valid")
|
||||||
|
rtc_status = "valid"
|
||||||
|
|
||||||
|
# Always convert to InfluxDB format
|
||||||
# Convert to InfluxDB RFC3339 format with UTC 'Z' suffix
|
# Convert to InfluxDB RFC3339 format with UTC 'Z' suffix
|
||||||
influx_timestamp = dt_object.strftime('%Y-%m-%dT%H:%M:%SZ')
|
influx_timestamp = dt_object.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||||
|
rtc_status = "valid"
|
||||||
print(influx_timestamp)
|
print(influx_timestamp)
|
||||||
|
|
||||||
#NEXTPM
|
#NEXTPM
|
||||||
print("➡️Getting NPM values (last 6 measures)")
|
print("➡️Getting NPM values (last 6 measures)")
|
||||||
#cursor.execute("SELECT * FROM data_NPM ORDER BY timestamp DESC LIMIT 1")
|
#cursor.execute("SELECT * FROM data_NPM ORDER BY timestamp DESC LIMIT 1")
|
||||||
cursor.execute("SELECT * FROM data_NPM ORDER BY timestamp DESC LIMIT 6")
|
#cursor.execute("SELECT * FROM data_NPM ORDER BY timestamp DESC LIMIT 6")
|
||||||
|
cursor.execute("SELECT rowid, * FROM data_NPM ORDER BY rowid DESC LIMIT 6")
|
||||||
|
|
||||||
rows = cursor.fetchall()
|
rows = cursor.fetchall()
|
||||||
# Exclude the timestamp column (assuming first column is timestamp)
|
# Exclude the timestamp column (assuming first column is timestamp)
|
||||||
data_values = [row[1:] for row in rows] # Exclude timestamp
|
data_values = [row[1:] for row in rows] # Exclude timestamp
|
||||||
@@ -396,12 +619,42 @@ try:
|
|||||||
print("Verify SARA R4 connection")
|
print("Verify SARA R4 connection")
|
||||||
|
|
||||||
# Getting the LTE Signal
|
# Getting the LTE Signal
|
||||||
print("-> Getting LTE signal <-")
|
print("➡️Getting LTE signal")
|
||||||
ser_sara.write(b'AT+CSQ\r')
|
ser_sara.write(b'AT+CSQ\r')
|
||||||
response2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
response2 = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR", "+CME ERROR"])
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(response2)
|
print(response2)
|
||||||
print("</p>")
|
print("</p>", end="")
|
||||||
|
|
||||||
|
#Here it's possible that the SARA do not repond at all or send a error message
|
||||||
|
#-> TO DO : harware reboot
|
||||||
|
#-> send notification
|
||||||
|
#-> end loop, no need to continue
|
||||||
|
|
||||||
|
#1. No answer at all form SARA
|
||||||
|
if response2 is None or response2 == "":
|
||||||
|
print("No answer from SARA module")
|
||||||
|
print('🛑STOP LOOP🛑')
|
||||||
|
print("<hr>")
|
||||||
|
|
||||||
|
#Send notification (WIFI)
|
||||||
|
send_error_notification(device_id, "serial_error")
|
||||||
|
|
||||||
|
#end loop
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
#2. si on a une erreur
|
||||||
|
elif "+CME ERROR" in response2:
|
||||||
|
print(f"SARA module returned error: {response2}")
|
||||||
|
print("The CSQ command is not supported by this module or in its current state")
|
||||||
|
print("⚠️ATTENTION: SARA is connected over serial but CSQ command not supported")
|
||||||
|
print('🛑STOP LOOP🛑')
|
||||||
|
#end loop
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
else :
|
||||||
|
print("✅SARA is connected over serial")
|
||||||
|
|
||||||
match = re.search(r'\+CSQ:\s*(\d+),', response2)
|
match = re.search(r'\+CSQ:\s*(\d+),', response2)
|
||||||
if match:
|
if match:
|
||||||
signal_quality = int(match.group(1))
|
signal_quality = int(match.group(1))
|
||||||
@@ -418,7 +671,7 @@ try:
|
|||||||
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20)
|
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20)
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(responseReconnect)
|
print(responseReconnect)
|
||||||
print("</p>")
|
print("</p>", end="")
|
||||||
|
|
||||||
print('🛑STOP LOOP🛑')
|
print('🛑STOP LOOP🛑')
|
||||||
print("<hr>")
|
print("<hr>")
|
||||||
@@ -439,14 +692,14 @@ try:
|
|||||||
print("Open JSON:")
|
print("Open JSON:")
|
||||||
command = f'AT+UDWNFILE="sensordata_csv.json",{size_of_string}\r'
|
command = f'AT+UDWNFILE="sensordata_csv.json",{size_of_string}\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=False)
|
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=True)
|
||||||
print(response_SARA_1)
|
print(response_SARA_1)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
#2. Write to shell
|
#2. Write to shell
|
||||||
print("Write data to memory:")
|
print("Write data to memory:")
|
||||||
ser_sara.write(csv_string.encode())
|
ser_sara.write(csv_string.encode())
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
||||||
print(response_SARA_2)
|
print(response_SARA_2)
|
||||||
|
|
||||||
#3. Send to endpoint (with device ID)
|
#3. Send to endpoint (with device ID)
|
||||||
@@ -454,11 +707,11 @@ try:
|
|||||||
command= f'AT+UHTTPC={aircarto_profile_id},4,"/pro_4G/data.php?sensor_id={device_id}&datetime={influx_timestamp}","server_response.txt","sensordata_csv.json",4\r'
|
command= f'AT+UHTTPC={aircarto_profile_id},4,"/pro_4G/data.php?sensor_id={device_id}&datetime={influx_timestamp}","server_response.txt","sensordata_csv.json",4\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
|
|
||||||
response_SARA_3 = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=120, wait_for_lines=["+UUHTTPCR", "+CME ERROR"], debug=True)
|
response_SARA_3 = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=120, wait_for_lines=["+UUHTTPCR", "+CME ERROR", "ERROR"], debug=True)
|
||||||
|
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(response_SARA_3)
|
print(response_SARA_3)
|
||||||
print("</p>")
|
print("</p>", end="")
|
||||||
|
|
||||||
# si on recoit la réponse UHTTPCR
|
# si on recoit la réponse UHTTPCR
|
||||||
if "+UUHTTPCR" in response_SARA_3:
|
if "+UUHTTPCR" in response_SARA_3:
|
||||||
@@ -485,8 +738,6 @@ try:
|
|||||||
print('<span style="color: red;font-weight: bold;">ATTENTION: CME ERROR</span>')
|
print('<span style="color: red;font-weight: bold;">ATTENTION: CME ERROR</span>')
|
||||||
print("error:", lines[-1])
|
print("error:", lines[-1])
|
||||||
print("*****")
|
print("*****")
|
||||||
#update status
|
|
||||||
update_json_key(config_file, "SARA_R4_network_status", "disconnected")
|
|
||||||
|
|
||||||
# Gestion de l'erreur spécifique
|
# Gestion de l'erreur spécifique
|
||||||
if "No connection to phone" in lines[-1]:
|
if "No connection to phone" in lines[-1]:
|
||||||
@@ -519,11 +770,10 @@ try:
|
|||||||
http_response = lines[-1] # "+UUHTTPCR: 0,4,0"
|
http_response = lines[-1] # "+UUHTTPCR: 0,4,0"
|
||||||
parts = http_response.split(',')
|
parts = http_response.split(',')
|
||||||
|
|
||||||
# 2.1 code 0 (HTTP failed)
|
# 2.1 code 0 (HTTP failed) ⛔⛔⛔
|
||||||
if len(parts) == 3 and parts[-1] == '0': # The third value indicates success
|
if len(parts) == 3 and parts[-1] == '0': # The third value indicates success
|
||||||
print("*****")
|
print("*****")
|
||||||
print('<span style="color: red;font-weight: bold;">ATTENTION: HTTP operation failed</span>')
|
print('<span style="color: red;font-weight: bold;">ATTENTION: HTTP operation failed</span>')
|
||||||
update_json_key(config_file, "SARA_R4_network_status", "disconnected")
|
|
||||||
print("*****")
|
print("*****")
|
||||||
print("Blink red LED")
|
print("Blink red LED")
|
||||||
# Run LED blinking in a separate thread
|
# Run LED blinking in a separate thread
|
||||||
@@ -537,44 +787,120 @@ try:
|
|||||||
response_SARA_9 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
response_SARA_9 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(response_SARA_9)
|
print(response_SARA_9)
|
||||||
print("</p>")
|
print("</p>", end="")
|
||||||
|
|
||||||
'''
|
# Extract just the error code
|
||||||
+UHTTPER: profile_id,error_class,error_code
|
error_code = extract_error_code(response_SARA_9)
|
||||||
|
if error_code is not None:
|
||||||
|
# Display interpretation based on error code
|
||||||
|
if error_code == 0:
|
||||||
|
print('<p class="text-success">No error detected</p>')
|
||||||
|
elif error_code == 4:
|
||||||
|
print('<p class="text-danger">Error 4: Invalid server Hostname</p>')
|
||||||
|
elif error_code == 11:
|
||||||
|
print('<p class="text-danger">Error 11: Server connection error</p>')
|
||||||
|
elif error_code == 22:
|
||||||
|
print('<p class="text-danger">⚠️Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️</p>')
|
||||||
|
elif error_code == 73:
|
||||||
|
print('<p class="text-danger">Error 73: Secure socket connect error</p>')
|
||||||
|
else:
|
||||||
|
print(f'<p class="text-danger">Unknown error code: {error_code}</p>')
|
||||||
|
else:
|
||||||
|
print('<p class="text-danger">Could not extract error code from response</p>')
|
||||||
|
|
||||||
error_class
|
|
||||||
0 OK, no error
|
|
||||||
3 HTTP Protocol error class
|
|
||||||
10 Wrong HTTP API USAGE
|
|
||||||
|
|
||||||
error_code (for error_class 3)
|
#Software Reboot
|
||||||
0 No error
|
software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||||
11 Server connection error
|
if software_reboot_success:
|
||||||
73 Secure socket connect error
|
print("Modem successfully rebooted and reinitialized")
|
||||||
'''
|
else:
|
||||||
|
print("There were issues with the modem reboot/reinitialize process")
|
||||||
|
|
||||||
#Pas forcément un moyen de résoudre le soucis
|
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
|
||||||
#print("resetting the URL (domain name):")
|
|
||||||
#command = f'AT+UHTTP={aircarto_profile_id},1,"{url_moduleair}"\r'
|
|
||||||
#ser_sara.write(command.encode('utf-8'))
|
|
||||||
#response_SARA_31 = read_complete_response(ser_sara)
|
|
||||||
#print(response_SARA_31)
|
|
||||||
|
|
||||||
# 2.2 code 1 (HHTP succeded)
|
|
||||||
else:
|
else:
|
||||||
# Si la commande HTTP a réussi
|
# Si la commande HTTP a réussi
|
||||||
print('<span class="badge text-bg-success">HTTP operation successful.</span>')
|
print('<span class="badge text-bg-success">✅✅HTTP operation successful.</span>')
|
||||||
update_json_key(config_file, "SARA_R4_network_status", "connected")
|
|
||||||
print("Blink blue LED")
|
print("Blink blue LED")
|
||||||
led_thread = Thread(target=blink_led, args=(23, 5, 0.5))
|
led_thread = Thread(target=blink_led, args=(23, 5, 0.5))
|
||||||
led_thread.start()
|
led_thread.start()
|
||||||
|
|
||||||
#4. Read reply from server
|
#4. Read reply from server
|
||||||
print("Reply from server:")
|
print("Reply from server:")
|
||||||
ser_sara.write(b'AT+URDFILE="server_response.txt"\r')
|
ser_sara.write(b'AT+URDFILE="server_response.txt"\r')
|
||||||
response_SARA_4 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
response_SARA_4 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||||
print('<p class="text-success">')
|
print('<p class="text-success">')
|
||||||
print(response_SARA_4)
|
print(response_SARA_4)
|
||||||
print('</p>')
|
print("</p>", end="")
|
||||||
|
|
||||||
|
#Parse the server datetime
|
||||||
|
# Extract just the date from the response
|
||||||
|
date_string = None
|
||||||
|
date_start = response_SARA_4.find("Date: ")
|
||||||
|
if date_start != -1:
|
||||||
|
date_end = response_SARA_4.find("\n", date_start)
|
||||||
|
date_string = response_SARA_4[date_start + 6:date_end].strip()
|
||||||
|
print(f'<div class="text-primary">Server date: {date_string}</div>', end="")
|
||||||
|
|
||||||
|
# Optionally convert to datetime object
|
||||||
|
try:
|
||||||
|
from datetime import datetime
|
||||||
|
server_datetime = datetime.strptime(
|
||||||
|
date_string,
|
||||||
|
"%a, %d %b %Y %H:%M:%S %Z"
|
||||||
|
)
|
||||||
|
#print(f'<p class="text-primary">Parsed datetime: {server_datetime}</p>')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'<p class="text-warning">Error parsing date: {e}</p>')
|
||||||
|
|
||||||
|
# Get RTC time from SQLite
|
||||||
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
rtc_time_str = row[1] # '2025-02-07 12:30:45' or '2000-01-01 00:55:21' or 'not connected'
|
||||||
|
print(f'<div class="text-primary">RTC time: {rtc_time_str}</div>', end="")
|
||||||
|
|
||||||
|
# Compare times if both are available
|
||||||
|
if server_datetime and rtc_time_str != 'not connected':
|
||||||
|
try:
|
||||||
|
# Convert RTC time string to datetime
|
||||||
|
rtc_datetime = datetime.strptime(rtc_time_str, '%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# Calculate time difference in seconds
|
||||||
|
time_diff = abs((server_datetime - rtc_datetime).total_seconds())
|
||||||
|
|
||||||
|
print(f'<div class="text-primary">Time difference: {time_diff:.2f} seconds</div>', end="")
|
||||||
|
|
||||||
|
# Check if difference is more than 60 seconds
|
||||||
|
# and update the RTC clock
|
||||||
|
if time_diff > 60:
|
||||||
|
print(f'<div class="text-warning"><strong>⚠️ RTC time differs from server time by {time_diff:.2f} seconds!</strong></div>', end="")
|
||||||
|
# Format server time for RTC update
|
||||||
|
server_time_formatted = server_datetime.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
#update RTC module do not wait for answer, non blocking
|
||||||
|
#/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39'
|
||||||
|
# Launch RTC update script as non-blocking subprocess
|
||||||
|
import subprocess
|
||||||
|
update_command = [
|
||||||
|
"/usr/bin/python3",
|
||||||
|
"/var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py",
|
||||||
|
server_time_formatted
|
||||||
|
]
|
||||||
|
|
||||||
|
# Execute the command without waiting for result
|
||||||
|
subprocess.Popen(update_command,
|
||||||
|
stdout=subprocess.DEVNULL,
|
||||||
|
stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
print(f'<div class="text-warning">➡️ Updating RTC with server time: {server_time_formatted}</div>', end="")
|
||||||
|
|
||||||
|
else:
|
||||||
|
print(f'<div class="text-success">✅ RTC time is synchronized with server time (within 60 seconds)</div>')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f'<p class="text-warning">Error comparing times: {e}</p>')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#Si non ne recoit pas de réponse UHTTPCR
|
#Si non ne recoit pas de réponse UHTTPCR
|
||||||
#on a peut etre une ERROR de type "+CME ERROR: No connection to phone"
|
#on a peut etre une ERROR de type "+CME ERROR: No connection to phone"
|
||||||
@@ -606,7 +932,7 @@ try:
|
|||||||
responseReconnect = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=120, wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
responseReconnect = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=120, wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(responseReconnect)
|
print(responseReconnect)
|
||||||
print("</p>")
|
print("</p>", end="")
|
||||||
# Handle "Operation not allowed" error
|
# Handle "Operation not allowed" error
|
||||||
if error_message == "Operation not allowed":
|
if error_message == "Operation not allowed":
|
||||||
print('<span style="color: orange;font-weight: bold;">❓Try Resetting the HTTP Profile❓</span>')
|
print('<span style="color: orange;font-weight: bold;">❓Try Resetting the HTTP Profile❓</span>')
|
||||||
@@ -615,18 +941,30 @@ try:
|
|||||||
responseResetHTTP_profile = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5, wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
responseResetHTTP_profile = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5, wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(responseResetHTTP_profile)
|
print(responseResetHTTP_profile)
|
||||||
print("</p>")
|
print("</p>", end="")
|
||||||
|
if "ERROR" in line:
|
||||||
|
print("⛔Attention ERROR!⛔")
|
||||||
|
#Send notification (WIFI)
|
||||||
|
send_error_notification(device_id, "sara_error")
|
||||||
|
|
||||||
|
#Software Reboot
|
||||||
|
software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||||
|
if software_reboot_success:
|
||||||
|
print("Modem successfully rebooted and reinitialized")
|
||||||
|
else:
|
||||||
|
print("There were issues with the modem reboot/reinitialize process")
|
||||||
|
|
||||||
|
|
||||||
#5. empty json
|
#5. empty json
|
||||||
print("Empty SARA memory:")
|
print("Empty SARA memory:")
|
||||||
ser_sara.write(b'AT+UDELFILE="sensordata_csv.json"\r')
|
ser_sara.write(b'AT+UDELFILE="sensordata_csv.json"\r')
|
||||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||||
|
print('<p class="text-danger-emphasis">')
|
||||||
print(response_SARA_5)
|
print(response_SARA_5)
|
||||||
|
print("</p>", end="")
|
||||||
|
|
||||||
|
if "+CME ERROR" in response_SARA_5:
|
||||||
|
print("⛔ Attention CME ERROR ⛔")
|
||||||
|
|
||||||
|
|
||||||
# Calculate and print the elapsed time
|
# Calculate and print the elapsed time
|
||||||
|
|||||||
Reference in New Issue
Block a user