diff --git a/README.md b/README.md
index 21b9fc4..eeb1d44 100755
--- a/README.md
+++ b/README.md
@@ -7,14 +7,13 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
sudo apt update
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 gh auth login
-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 git clone http://gitea.aircarto.fr/PaulVua/moduleair_pro_4g.git /var/www/moduleair_pro_4g
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 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/
+git config core.fileMode false
+
```
## Apache
diff --git a/SARA/reboot/start.py b/SARA/reboot/start.py
index 7b37c10..db8f135 100644
--- a/SARA/reboot/start.py
+++ b/SARA/reboot/start.py
@@ -17,52 +17,96 @@ import time
import sys
import json
import re
+import sqlite3
-#get data from config
-def load_config(config_file):
+# database connection
+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:
- 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
+
except Exception as e:
- print(f"Error loading config file: {e}")
+ print(f"Error loading config from SQLite: {e}")
return {}
-#Fonction pour mettre à jour le JSON de configuration
-def update_json_key(file_path, key, value):
+def update_sqlite_config(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 JSON file.
+ :param key: The key to update in the config_table.
: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 and get its type
+ cursor.execute("SELECT type FROM config_table WHERE key = ?", (key,))
+ result = cursor.fetchone()
- # 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.")
+ if result is None:
+ print(f"Key '{key}' not found in the config_table.")
+ conn.close()
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
+ # Get the type of the value from the database
+ value_type = result[0]
- 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:
- print(f"Error updating the JSON file: {e}")
+ print(f"Error updating the SQLite database: {e}")
-# Define the config file path
-config_file = '/var/www/moduleair_pro_4g/config.json'
-# Load the configuration data
-config = load_config(config_file)
+#Load config
+config = load_config_sqlite()
+#config
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
device_id = config.get('deviceID', '').upper() #device ID en maj
+sara_r5_DPD_setup = False
ser_sara = serial.Serial(
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
+
+
try:
print('
Start reboot python script
')
#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")
command = f'ATI\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_ATI = read_complete_response(ser_sara, wait_for_lines=["IMEI"])
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
- print(f" Model: {model}")
- update_json_key(config_file, "modem_version", model)
+
+ # Check for SARA model with more robust regex
+ model = "Unknown"
+ 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)
+ '''
+ AIRCARTO
+ '''
# 1. Set AIRCARTO URL
print('➡️Set aircarto URL')
@@ -143,6 +212,10 @@ try:
print(response_SARA_1)
time.sleep(1)
+ '''
+ uSpot
+ '''
+
#2. Set uSpot URL
print('➡️Set uSpot URL')
uSpot_profile_id = 1
@@ -179,9 +252,9 @@ try:
else:
print("❌ Failed to extract coordinates.")
- #update config.json
- update_json_key(config_file, "latitude_raw", float(latitude))
- update_json_key(config_file, "longitude_raw", float(longitude))
+ #update sqlite table
+ update_sqlite_config("latitude_raw", float(latitude))
+ update_sqlite_config("longitude_raw", float(longitude))
time.sleep(1)
diff --git a/SARA/sara.py b/SARA/sara.py
index e6e0996..cfc5030 100755
--- a/SARA/sara.py
+++ b/SARA/sara.py
@@ -28,11 +28,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
command = parameter[1] # ex: AT+CCID?
timeout = float(parameter[2]) # ex:2
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_connectNetwork.py b/SARA/sara_connectNetwork.py
index 73b79d2..ea94fa1 100755
--- a/SARA/sara_connectNetwork.py
+++ b/SARA/sara_connectNetwork.py
@@ -24,11 +24,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
networkID = parameter[1] # ex: 20801
timeout = float(parameter[2]) # ex:2
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_eraseMessage.py b/SARA/sara_eraseMessage.py
index 262740f..a53f015 100755
--- a/SARA/sara_eraseMessage.py
+++ b/SARA/sara_eraseMessage.py
@@ -17,11 +17,9 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2
message = parameter[1] # ex: Hello
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_readMessage.py b/SARA/sara_readMessage.py
index 421e250..847fb08 100755
--- a/SARA/sara_readMessage.py
+++ b/SARA/sara_readMessage.py
@@ -16,11 +16,9 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2
message = parameter[1] # ex: Hello
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_sendMessage.py b/SARA/sara_sendMessage.py
index 46864c7..7018fad 100755
--- a/SARA/sara_sendMessage.py
+++ b/SARA/sara_sendMessage.py
@@ -18,11 +18,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
endpoint = parameter[1] # ex: /pro_4G/notif_message.php
profile_id = parameter[2]
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_setAPN.py b/SARA/sara_setAPN.py
index 7104c18..e7d6df3 100755
--- a/SARA/sara_setAPN.py
+++ b/SARA/sara_setAPN.py
@@ -22,12 +22,9 @@ apn_address = parameter[1] # ex: data.mono
timeout = float(parameter[2]) # ex:2
-baudrate = 9600
-
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_setURL.py b/SARA/sara_setURL.py
index 68f1868..7798878 100755
--- a/SARA/sara_setURL.py
+++ b/SARA/sara_setURL.py
@@ -27,12 +27,9 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
url = parameter[1] # ex: data.mobileair.fr
profile_id = parameter[2] #ex: 0
-
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_setURL_uSpot_noSSL.py b/SARA/sara_setURL_uSpot_noSSL.py
index b0b8af9..8d71295 100755
--- a/SARA/sara_setURL_uSpot_noSSL.py
+++ b/SARA/sara_setURL_uSpot_noSSL.py
@@ -46,11 +46,9 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
return response.decode('utf-8', errors='replace')
-baudrate = 9600
-
ser_sara = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/SARA/sara_writeMessage.py b/SARA/sara_writeMessage.py
index 4e0d6f9..2fe4a91 100755
--- a/SARA/sara_writeMessage.py
+++ b/SARA/sara_writeMessage.py
@@ -17,12 +17,9 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2
message = parameter[1] # ex: Hello
-
-baudrate = 9600
-
ser = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
diff --git a/loop/SARA_send_data_v2.py b/loop/SARA_send_data_v2.py
index 93732cf..06515bc 100755
--- a/loop/SARA_send_data_v2.py
+++ b/loop/SARA_send_data_v2.py
@@ -156,60 +156,88 @@ def blink_led(pin, blink_count, delay=1):
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):
+#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:
- 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
+
except Exception as e:
- print(f"Error loading config file: {e}")
+ print(f"Error loading config from SQLite: {e}")
return {}
-#Fonction pour mettre à jour le JSON de configuration
-def update_json_key(file_path, key, value):
+def load_config_scripts_sqlite():
"""
- 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.
+ Load script configuration data from SQLite config_scripts_table
+
+ Returns:
+ dict: Script paths as keys and enabled status as boolean values
"""
try:
- # Load the existing data
- with open(file_path, "r") as file:
- data = json.load(file)
+ # Query the config_scripts_table
+ cursor.execute("SELECT script_path, enabled FROM config_scripts_table")
+ rows = cursor.fetchall()
- # 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
+ # Create config dictionary with script paths as keys and enabled status as boolean values
+ scripts_config = {}
+ for script_path, enabled in rows:
+ # Convert integer enabled value (0/1) to boolean
+ scripts_config[script_path] = bool(enabled)
- # 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
+ return scripts_config
- print(f"updating '{key}' to '{value}'.")
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
config_file = '/var/www/moduleair_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
-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.moduleair.fr)
-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
+#Load config
+config = load_config_sqlite()
+#config
+device_id = config.get('deviceID', 'unknown')
+device_id = device_id.upper()
+modem_config_mode = config.get('modem_config_mode', False)
+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 ()
-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
payload_json["moduleairid"] = device_id
@@ -221,7 +249,7 @@ if modem_config_mode:
ser_sara = serial.Serial(
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
stopbits=serial.STOPBITS_ONE,
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
+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('🔄 Complete SARA reboot and reinitialize sequence 🔄')
+
+ # Step 1: Reboot the modem - Integrated modem_software_reboot logic
+ print('🔄 Software SARA reboot! 🔄')
+
+ # 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('')
+ print(response)
+ print("
", 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('🔧 Resetting the HTTP Profile')
+ 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('')
+ print(responseResetHTTP)
+ print("
", 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:
'''
_ ___ ___ ____
@@ -288,16 +492,35 @@ try:
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'
- # Convert to a datetime object
- dt_object = datetime.strptime(rtc_time_str, '%Y-%m-%d %H:%M:%S')
- # Convert to InfluxDB RFC3339 format with UTC 'Z' suffix
- influx_timestamp = dt_object.strftime('%Y-%m-%dT%H:%M:%SZ')
- print(influx_timestamp)
+ 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
+ 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
+ influx_timestamp = dt_object.strftime('%Y-%m-%dT%H:%M:%SZ')
+ rtc_status = "valid"
+ print(influx_timestamp)
#NEXTPM
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 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()
# Exclude the timestamp column (assuming first column is timestamp)
data_values = [row[1:] for row in rows] # Exclude timestamp
@@ -396,12 +619,42 @@ try:
print("Verify SARA R4 connection")
# Getting the LTE Signal
- print("-> Getting LTE signal <-")
+ print("➡️Getting LTE signal")
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('')
print(response2)
- print("
")
+ print("", 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("
")
+
+ #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)
if match:
signal_quality = int(match.group(1))
@@ -418,7 +671,7 @@ try:
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20)
print('')
print(responseReconnect)
- print("
")
+ print("", end="")
print('🛑STOP LOOP🛑')
print("
")
@@ -439,14 +692,14 @@ try:
print("Open JSON:")
command = f'AT+UDWNFILE="sensordata_csv.json",{size_of_string}\r'
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)
time.sleep(1)
#2. Write to shell
print("Write data to memory:")
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)
#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'
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('')
print(response_SARA_3)
- print("
")
+ print("", end="")
# si on recoit la réponse UHTTPCR
if "+UUHTTPCR" in response_SARA_3:
@@ -485,9 +738,7 @@ try:
print('ATTENTION: CME ERROR')
print("error:", lines[-1])
print("*****")
- #update status
- update_json_key(config_file, "SARA_R4_network_status", "disconnected")
-
+
# Gestion de l'erreur spécifique
if "No connection to phone" in lines[-1]:
print("No connection to the phone. Retrying or reset may be required.")
@@ -519,11 +770,10 @@ try:
http_response = lines[-1] # "+UUHTTPCR: 0,4,0"
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
print("*****")
print('ATTENTION: HTTP operation failed')
- update_json_key(config_file, "SARA_R4_network_status", "disconnected")
print("*****")
print("Blink red LED")
# 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)
print('')
print(response_SARA_9)
- print("
")
+ print("", end="")
- '''
- +UHTTPER: profile_id,error_class,error_code
+ # Extract just the 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('No error detected
')
+ elif error_code == 4:
+ print('Error 4: Invalid server Hostname
')
+ elif error_code == 11:
+ print('Error 11: Server connection error
')
+ elif error_code == 22:
+ print('⚠️Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️
')
+ elif error_code == 73:
+ print('Error 73: Secure socket connect error
')
+ else:
+ print(f'Unknown error code: {error_code}
')
+ else:
+ print('Could not extract error code from response
')
+
- error_class
- 0 OK, no error
- 3 HTTP Protocol error class
- 10 Wrong HTTP API USAGE
+ #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")
- error_code (for error_class 3)
- 0 No error
- 11 Server connection error
- 73 Secure socket connect error
- '''
-
- #Pas forcément un moyen de résoudre le soucis
- #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)
+ # 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
else:
# Si la commande HTTP a réussi
- print('HTTP operation successful.')
- update_json_key(config_file, "SARA_R4_network_status", "connected")
+ print('✅✅HTTP operation successful.')
print("Blink blue LED")
led_thread = Thread(target=blink_led, args=(23, 5, 0.5))
led_thread.start()
+
#4. Read reply from server
print("Reply from server:")
ser_sara.write(b'AT+URDFILE="server_response.txt"\r')
response_SARA_4 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
print('')
print(response_SARA_4)
- print('
')
+ print("", 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'Server date: {date_string}
', 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'Parsed datetime: {server_datetime}
')
+ except Exception as e:
+ print(f'Error parsing date: {e}
')
+
+ # 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'RTC time: {rtc_time_str}
', 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'Time difference: {time_diff:.2f} seconds
', end="")
+
+ # Check if difference is more than 60 seconds
+ # and update the RTC clock
+ if time_diff > 60:
+ print(f'⚠️ RTC time differs from server time by {time_diff:.2f} seconds!
', 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'➡️ Updating RTC with server time: {server_time_formatted}
', end="")
+
+ else:
+ print(f'✅ RTC time is synchronized with server time (within 60 seconds)
')
+
+ except Exception as e:
+ print(f'Error comparing times: {e}
')
+
+
+
#Si non ne recoit pas de réponse UHTTPCR
#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)
print('')
print(responseReconnect)
- print("
")
+ print("", end="")
# Handle "Operation not allowed" error
if error_message == "Operation not allowed":
print('❓Try Resetting the HTTP Profile❓')
@@ -615,20 +941,32 @@ try:
responseResetHTTP_profile = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5, wait_for_lines=["OK", "+CME ERROR"], debug=True)
print('')
print(responseResetHTTP_profile)
- print("
")
+ print("", 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
print("Empty SARA memory:")
ser_sara.write(b'AT+UDELFILE="sensordata_csv.json"\r')
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
+ print('')
print(response_SARA_5)
+ print("
", end="")
+
+ if "+CME ERROR" in response_SARA_5:
+ print("⛔ Attention CME ERROR ⛔")
-
-
-
# Calculate and print the elapsed time
elapsed_time = time.time() - start_time_script
print(f"Elapsed time: {elapsed_time:.2f} seconds")