10 Commits

Author SHA1 Message Date
Your Name
33b24a9f53 update 2025-03-10 15:00:00 +01:00
Your Name
10c4348e54 update 2025-03-10 13:38:02 +01:00
Your Name
072f98ef95 update 2025-03-10 12:15:05 +01:00
Your Name
7b4ff011ec update 2025-03-05 16:24:15 +01:00
Your Name
ab2124f50d update 2025-03-05 16:19:49 +01:00
Your Name
b493d30a41 update 2025-03-05 16:07:22 +01:00
Your Name
659effb7c4 update 2025-03-05 15:58:40 +01:00
Your Name
ebb0fd0a2b update 2025-03-05 09:29:53 +01:00
Your Name
5d121761e7 update 2025-03-04 13:29:02 +01:00
Your Name
d90fb14c90 update 2025-03-04 13:23:26 +01:00
11 changed files with 583 additions and 82 deletions

View File

@@ -1,11 +1,15 @@
#!/usr/bin/python3
""" """
Script to set the RTC using an NTP server. ____ _____ ____
| _ \_ _/ ___|
| |_) || || |
| _ < | || |___
|_| \_\|_| \____|
Script to set the RTC using an NTP server (script used by web UI)
RPI needs to be connected to the internet (WIFI). RPI needs to be connected to the internet (WIFI).
Requires ntplib and pytz: Requires ntplib and pytz:
sudo pip3 install ntplib pytz --break-system-packages sudo pip3 install ntplib pytz --break-system-packages
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
""" """
import smbus2 import smbus2
import time import time
@@ -49,29 +53,95 @@ def set_time(bus, year, month, day, hour, minute, second):
]) ])
def read_time(bus): def read_time(bus):
"""Read the RTC time.""" """Read the RTC time and validate the values."""
try:
data = bus.read_i2c_block_data(DS3231_ADDR, REG_TIME, 7) data = bus.read_i2c_block_data(DS3231_ADDR, REG_TIME, 7)
# Convert from BCD
second = bcd_to_dec(data[0] & 0x7F) second = bcd_to_dec(data[0] & 0x7F)
minute = bcd_to_dec(data[1]) minute = bcd_to_dec(data[1])
hour = bcd_to_dec(data[2] & 0x3F) hour = bcd_to_dec(data[2] & 0x3F)
day = bcd_to_dec(data[4]) day = bcd_to_dec(data[4])
month = bcd_to_dec(data[5]) month = bcd_to_dec(data[5])
year = bcd_to_dec(data[6]) + 2000 year = bcd_to_dec(data[6]) + 2000
# Print raw values for debugging
print(f"Raw RTC values: {data}")
print(f"Decoded values: Y:{year} M:{month} D:{day} H:{hour} M:{minute} S:{second}")
# Validate date values
if not (1 <= month <= 12):
print(f"Invalid month value: {month}, using default")
month = 1
# Check days in month (simplified)
days_in_month = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if not (1 <= day <= days_in_month[month]):
print(f"Invalid day value: {day} for month {month}, using default")
day = 1
# Validate time values
if not (0 <= hour <= 23):
print(f"Invalid hour value: {hour}, using default")
hour = 0
if not (0 <= minute <= 59):
print(f"Invalid minute value: {minute}, using default")
minute = 0
if not (0 <= second <= 59):
print(f"Invalid second value: {second}, using default")
second = 0
return (year, month, day, hour, minute, second) return (year, month, day, hour, minute, second)
except Exception as e:
print(f"Error reading RTC: {e}")
# Return a safe default date (2023-01-01 00:00:00)
return (2023, 1, 1, 0, 0, 0)
def get_internet_time(): def get_internet_time():
"""Get the current time from an NTP server.""" """Get the current time from an NTP server."""
ntp_client = ntplib.NTPClient() ntp_client = ntplib.NTPClient()
response = ntp_client.request('pool.ntp.org') # Try multiple NTP servers in case one fails
servers = ['pool.ntp.org', 'time.google.com', 'time.windows.com', 'time.apple.com']
for server in servers:
try:
print(f"Trying NTP server: {server}")
response = ntp_client.request(server, timeout=2)
utc_time = datetime.utcfromtimestamp(response.tx_time) utc_time = datetime.utcfromtimestamp(response.tx_time)
print(f"Successfully got time from {server}")
return utc_time return utc_time
except Exception as e:
print(f"Failed to get time from {server}: {e}")
# If all servers fail, raise exception
raise Exception("All NTP servers failed")
def main(): def main():
try:
bus = smbus2.SMBus(1) bus = smbus2.SMBus(1)
# Test if RTC is accessible
try:
bus.read_byte(DS3231_ADDR)
print("RTC module is accessible")
except Exception as e:
print(f"Error accessing RTC module: {e}")
print("Please check connections and I2C configuration")
return
# Get the current time from the RTC # Get the current time from the RTC
try:
year, month, day, hours, minutes, seconds = read_time(bus) year, month, day, hours, minutes, seconds = read_time(bus)
# Create datetime object with validation to handle invalid dates
rtc_time = datetime(year, month, day, hours, minutes, seconds) rtc_time = datetime(year, month, day, hours, minutes, seconds)
print(f"Actual RTC Time : {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}")
except ValueError as e:
print(f"Invalid date/time read from RTC: {e}")
print("Will proceed with setting RTC from internet time")
rtc_time = None
# Get current UTC time from an NTP server # Get current UTC time from an NTP server
try: try:
@@ -79,19 +149,35 @@ def main():
print(f"Time from Internet (UTC) : {internet_utc_time.strftime('%Y-%m-%d %H:%M:%S')}") print(f"Time from Internet (UTC) : {internet_utc_time.strftime('%Y-%m-%d %H:%M:%S')}")
except Exception as e: except Exception as e:
print(f"Error retrieving time from the internet: {e}") print(f"Error retrieving time from the internet: {e}")
if rtc_time is None:
print("Cannot proceed without either valid RTC time or internet time")
return
print("Will keep current RTC time")
return return
# Print current RTC time
print(f"Actual RTC Time : {rtc_time.strftime('%Y-%m-%d %H:%M:%S')}")
# Set the RTC to UTC time # Set the RTC to UTC time
print("Setting RTC to internet time...")
set_time(bus, internet_utc_time.year, internet_utc_time.month, internet_utc_time.day, set_time(bus, internet_utc_time.year, internet_utc_time.month, internet_utc_time.day,
internet_utc_time.hour, internet_utc_time.minute, internet_utc_time.second) internet_utc_time.hour, internet_utc_time.minute, internet_utc_time.second)
# Read and print the new time from RTC # Read and print the new time from RTC
print("Reading back new RTC time...")
year, month, day, hour, minute, second = read_time(bus) year, month, day, hour, minute, second = read_time(bus)
rtc_time_new = datetime(year, month, day, hour, minute, second) rtc_time_new = datetime(year, month, day, hour, minute, second)
print(f"New RTC Time (UTC) : {rtc_time_new.strftime('%Y-%m-%d %H:%M:%S')}") print(f"New RTC Time (UTC) : {rtc_time_new.strftime('%Y-%m-%d %H:%M:%S')}")
# Calculate difference to verify accuracy
time_diff = abs((rtc_time_new - internet_utc_time).total_seconds())
print(f"Time difference : {time_diff:.2f} seconds")
if time_diff > 5:
print("Warning: RTC time differs significantly from internet time")
print("You may need to retry or check RTC module")
else:
print("RTC successfully synchronized with internet time")
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@@ -1,5 +1,11 @@
""" """
Script to set the RTC using the browser time. ____ _____ ____
| _ \_ _/ ___|
| |_) || || |
| _ < | || |___
|_| \_\|_| \____|
Script to set the RTC using the browser time (script used by the web UI).
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39' /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39'

View File

@@ -0,0 +1,103 @@
'''
____ _ ____ _
/ ___| / \ | _ \ / \
\___ \ / _ \ | |_) | / _ \
___) / ___ \| _ < / ___ \
|____/_/ \_\_| \_\/_/ \_\
Script to Configures the network connection to a Multi GNSS Assistance (MGA) server used also per CellLocate
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/cellLocate/server_conf.py ttyAMA2 1
AT+UGSRV="cell-live1.services.u-blox.com","cell-live2.services.u-blox.com","XkEKfGqVSbmNE1eZfBZm4Q"
'''
import serial
import time
import sys
import json
parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2
timeout = float(parameter[1]) # ex:2
#get baudrate
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 {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial(
port=port, #USB0 or ttyS0
baudrate=baudrate, #115200 ou 9600
parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout = timeout
)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
'''
Fonction très importante !!!
Reads the complete response from a serial connection and waits for specific lines.
'''
if wait_for_lines is None:
wait_for_lines = [] # Default to an empty list if not provided
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 any target line
decoded_response = response.decode('utf-8', errors='replace')
for target_line in wait_for_lines:
if target_line in decoded_response:
if debug:
print(f"[DEBUG] 🔎 Found target line: {target_line} (in {elapsed_time:.2f}s)")
return decoded_response # Return response immediately if a target line is found
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') # Return the full response if no target line is found
#command = f'ATI\r'
command = f'AT+UGSRV="cell-live1.services.u-blox.com","cell-live2.services.u-blox.com","XkEKfGqVSbmNE1eZfBZm4Q"\r'
ser.write((command + '\r').encode('utf-8'))
response = read_complete_response(ser, wait_for_lines=["+UULOC"])
print(response)

View File

@@ -121,19 +121,38 @@ 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"
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")
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_json_key(config_file, "modem_version", model) update_json_key(config_file, "modem_version", model)
time.sleep(1) time.sleep(1)
# 1. Set AIRCARTO URL # 1. Set AIRCARTO URL (profile id = 0)
print('Set aircarto URL') print('Set aircarto URL')
aircarto_profile_id = 0 aircarto_profile_id = 0
aircarto_url="data.nebuleair.fr" aircarto_url="data.nebuleair.fr"
@@ -143,26 +162,74 @@ try:
print(response_SARA_1) print(response_SARA_1)
time.sleep(1) time.sleep(1)
#2. Set uSpot URL #2. Set uSpot URL (profile id = 1)
print('Set uSpot URL') print('Set uSpot URL')
uSpot_profile_id = 1 uSpot_profile_id = 1
uSpot_url="api-prod.uspot.probesys.net" uSpot_url="api-prod.uspot.probesys.net"
security_profile_id = 1
#step 1: import the certificate
print("****")
certificate_name = "e6"
with open("/var/www/nebuleair_pro_4g/SARA/SSL/certificate/e6.pem", "rb") as cert_file:
certificate = cert_file.read()
size_of_string = len(certificate)
print("\033[0;33m Import certificate\033[0m")
# AT+USECMNG=0,<type>,<internal_name>,<data_size>
# type-> 0 -> trusted root CA
command = f'AT+USECMNG=0,0,"{certificate_name}",{size_of_string}\r'
ser_sara.write((command + '\r').encode('utf-8'))
response_SARA_1 = read_complete_response(ser_sara)
print(response_SARA_1)
time.sleep(0.5)
print("\033[0;33mAdd certificate\033[0m")
ser_sara.write(certificate)
response_SARA_2 = read_complete_response(ser_sara)
print(response_SARA_2)
time.sleep(0.5)
# SECURITY PROFILE
# op_code: 3 -> trusted root certificate internal name
print("\033[0;33mSet the security profile (choose cert)\033[0m")
command = f'AT+USECPRF={security_profile_id},3,"{certificate_name}"\r'
ser_sara.write((command + '\r').encode('utf-8'))
response_SARA_5c = read_complete_response(ser_sara, wait_for_lines=["OK"])
print(response_SARA_5c)
time.sleep(0.5)
#step 4: set url (op_code = 1)
command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r' command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"]) response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
print(response_SARA_2) print(response_SARA_2)
time.sleep(1) time.sleep(1)
print("set port 81") #step 4: set PORT (op_code = 5)
command = f'AT+UHTTP={uSpot_profile_id},5,81\r' print("set port 443")
command = f'AT+UHTTP={uSpot_profile_id},5,443\r'
ser_sara.write((command + '\r').encode('utf-8')) ser_sara.write((command + '\r').encode('utf-8'))
response_SARA_55 = read_complete_response(ser_sara, wait_for_lines=["OK"]) response_SARA_55 = read_complete_response(ser_sara, wait_for_lines=["OK"])
print(response_SARA_55) print(response_SARA_55)
time.sleep(1)
#step 4: set url to SSL (op_code = 6) (http_secure = 1 for HTTPS)(USECMNG_PROFILE = 2)
print("\033[0;33mSET SSL\033[0m")
http_secure = 1
command = f'AT+UHTTP={uSpot_profile_id},6,{http_secure},{security_profile_id}\r'
#command = f'AT+UHTTP={profile_id},6,{http_secure}\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
print(response_SARA_5)
time.sleep(1) time.sleep(1)
#3. Get localisation (CellLocate) #3. Get localisation (CellLocate)
mode = 2 mode = 2 #single shot position
sensor = 2 sensor = 2 #use cellular CellLocate® location information
response_type = 0 response_type = 0
timeout_s = 2 timeout_s = 2
accuracy_m = 1 accuracy_m = 1

View File

@@ -7,6 +7,8 @@
Script to see if the SARA-R410 is running Script to see if the SARA-R410 is running
ex: ex:
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 ATI 2
ex 1 (get SIM infos)
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+CCID? 2 python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+CCID? 2
ex 2 (turn on blue light): ex 2 (turn on blue light):
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UGPIOC=16,2 2 python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UGPIOC=16,2 2
@@ -14,6 +16,8 @@ ex 3 (reconnect network)
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+COPS=1,2,20801 20 python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+COPS=1,2,20801 20
ex 4 (get HTTP Profiles) ex 4 (get HTTP Profiles)
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UHTTP? 2 python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+UHTTP? 2
ex 5 (get IP addr)
python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 AT+CGPADDR=1 2
''' '''

79
SARA/sara_checkDNS.py Normal file
View File

@@ -0,0 +1,79 @@
'''
____ _ ____ _
/ ___| / \ | _ \ / \
\___ \ / _ \ | |_) | / _ \
___) / ___ \| _ < / ___ \
|____/_/ \_\_| \_\/_/ \_\
Script to resolve DNS (get IP from domain name) with AT+UDNSRN command
Ex:
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_checkDNS.py ttyAMA2 data.nebuleair.fr
To do: need to add profile id as parameter
'''
import serial
import time
import sys
import json
parameter = sys.argv[1:] # Exclude the script name
#print("Parameters received:")
port='/dev/'+parameter[0] # ex: ttyAMA2
url = parameter[1] # ex: data.mobileair.fr
#get baudrate
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 {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial(
port=port, #USB0 or ttyS0
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
)
command = f'AT+UDNSRN=0,"{url}"\r'
ser.write((command + '\r').encode('utf-8'))
print("****")
print("DNS check")
try:
# Read lines until a timeout occurs
response_lines = []
while True:
line = ser.readline().decode('utf-8').strip()
if not line:
break # Break the loop if an empty line is encountered
response_lines.append(line)
# Print the response
for line in response_lines:
print(line)
except serial.SerialException as e:
print(f"Error: {e}")
finally:
if ser.is_open:
ser.close()
print("****")
#print("Serial closed")

View File

@@ -49,6 +49,8 @@ ser = serial.Serial(
) )
command = f'AT+CGDCONT=1,"IP","{apn_address}"\r' command = f'AT+CGDCONT=1,"IP","{apn_address}"\r'
#command = f'AT+CGDCONT=1,"IPV4V6","{apn_address}"\r'
#command = f'AT+CGDCONT=1,"IP","{apn_address}",0,0\r'
ser.write((command + '\r').encode('utf-8')) ser.write((command + '\r').encode('utf-8'))

View File

@@ -7,6 +7,7 @@
"envea/read_value_v2.py": false, "envea/read_value_v2.py": false,
"sqlite/flush_old_data.py": true, "sqlite/flush_old_data.py": true,
"deviceID": "XXXX", "deviceID": "XXXX",
"npm_5channel": false,
"latitude_raw": 0, "latitude_raw": 0,
"longitude_raw":0, "longitude_raw":0,
"latitude_precision": 0, "latitude_precision": 0,
@@ -25,7 +26,7 @@
"SARA_R4_general_status": "connected", "SARA_R4_general_status": "connected",
"SARA_R4_SIM_status": "connected", "SARA_R4_SIM_status": "connected",
"SARA_R4_network_status": "connected", "SARA_R4_network_status": "connected",
"SARA_R4_neworkID": 0, "SARA_R4_neworkID": 20810,
"WIFI_status": "connected", "WIFI_status": "connected",
"MQTT_GUI": false, "MQTT_GUI": false,
"send_aircarto": true, "send_aircarto": true,

View File

@@ -268,9 +268,10 @@ if ($type == "sara_connectNetwork") {
$port=$_GET['port']; $port=$_GET['port'];
$timeout=$_GET['timeout']; $timeout=$_GET['timeout'];
$networkID=$_GET['networkID']; $networkID=$_GET['networkID'];
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout;
$output = shell_exec($command); echo "updating SARA_R4_networkID in config file";
echo $output; // Convert `networkID` to an integer (or float if needed)
$networkID = is_numeric($networkID) ? (strpos($networkID, '.') !== false ? (float)$networkID : (int)$networkID) : 0;
#save to config.json #save to config.json
$configFile = '/var/www/nebuleair_pro_4g/config.json'; $configFile = '/var/www/nebuleair_pro_4g/config.json';
// Read the JSON file // Read the JSON file
@@ -297,6 +298,13 @@ if ($type == "sara_connectNetwork") {
echo "SARA_R4_networkID updated successfully."; echo "SARA_R4_networkID updated successfully.";
echo "connecting to network... please wait...";
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout;
$output = shell_exec($command);
echo $output;
} }
#SET THE URL for messaging (profile id 2) #SET THE URL for messaging (profile id 2)

View File

@@ -27,6 +27,10 @@ fi
info "Set up the RTC" info "Set up the RTC"
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
#Check SARA R4 connection
info "Check SARA R4 connection"
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 ATI 2
#set up SARA R4 APN #set up SARA R4 APN
info "Set up Monogoto APN" info "Set up Monogoto APN"
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setAPN.py ttyAMA2 data.mono 2 /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_setAPN.py ttyAMA2 data.mono 2
@@ -39,7 +43,11 @@ info "Activate blue LED"
info "Connect SARA R4 to network" info "Connect SARA R4 to network"
python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 20810 60 python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 20810 60
#Add master_nebuleair.service #Need to create the two service
# 1. master_nebuleair
# 2. rtc_save_to_db
#1. Add master_nebuleair.service
SERVICE_FILE="/etc/systemd/system/master_nebuleair.service" SERVICE_FILE="/etc/systemd/system/master_nebuleair.service"
info "Setting up systemd service for master_nebuleair..." info "Setting up systemd service for master_nebuleair..."
@@ -74,3 +82,41 @@ sudo systemctl enable master_nebuleair.service
# Start the service immediately # Start the service immediately
info "Starting the service..." info "Starting the service..."
sudo systemctl start master_nebuleair.service sudo systemctl start master_nebuleair.service
#2. Add master_nebuleair.service
SERVICE_FILE_2="/etc/systemd/system/rtc_save_to_db.service"
info "Setting up systemd service for rtc_save_to_db..."
# Create the systemd service file (overwrite if necessary)
sudo bash -c "cat > $SERVICE_FILE_2" <<EOF
[Unit]
Description=RTC Save to DB Script
After=network.target
[Service]
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/save_to_db.py
Restart=always
RestartSec=1
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/rtc_save_to_db.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/rtc_save_to_db_errors.log
[Install]
WantedBy=multi-user.target
EOF
success "Systemd service file created: $SERVICE_FILE_2"
# Reload systemd to recognize the new service
info "Reloading systemd daemon..."
sudo systemctl daemon-reload
# Enable the service to start on boot
info "Enabling the service to start on boot..."
sudo systemctl enable rtc_save_to_db.service
# Start the service immediately
info "Starting the service..."
sudo systemctl start rtc_save_to_db.service

View File

@@ -212,7 +212,7 @@ bme_280_config = config.get('BME280/get_data_v2.py', False) #présence d
envea_cairsens= config.get('envea/read_value_v2.py', False) envea_cairsens= config.get('envea/read_value_v2.py', False)
send_aircarto = config.get('send_aircarto', True) #envoi sur AirCarto (data.nebuleair.fr) send_aircarto = config.get('send_aircarto', True) #envoi sur AirCarto (data.nebuleair.fr)
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', '') selected_networkID = int(config.get('SARA_R4_neworkID', 0))
npm_5channel = config.get('NextPM_5channels', False) #5 canaux du NPM 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 modem_config_mode = config.get('modem_config_mode', False) #modem 4G en mode configuration
@@ -290,23 +290,45 @@ try:
print('<h3>START LOOP</h3>') print('<h3>START LOOP</h3>')
#Local timestamp #Local timestamp
#ATTENTION:
# -> RTC module can be deconnected ""
# -> RTC module can be out of time like "2000-01-01T00:55:21Z"
print("Getting local timestamp") print("Getting local timestamp")
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' ou '2000-01-01 00:55:21' ou 'not connected'
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
# We take the last measures (order by rowid and not by timestamp)
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[2:] for row in rows] # Exclude timestamp
# Compute column-wise average # Compute column-wise average
num_columns = len(data_values[0]) num_columns = len(data_values[0])
averages = [round(sum(col) / len(col),1) for col in zip(*data_values)] averages = [round(sum(col) / len(col),1) for col in zip(*data_values)]
@@ -407,7 +429,7 @@ try:
response2 = read_complete_response(ser_sara, wait_for_lines=["OK"]) response2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
print('<p class="text-danger-emphasis">') print('<p class="text-danger-emphasis">')
print(response2) print(response2)
print("</p>") print("</p>", end="")
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))
@@ -419,12 +441,13 @@ try:
if signal_quality == 99: if signal_quality == 99:
print('<span style="color: red;font-weight: bold;">⚠ATTENTION: Signal Quality indicates no signal (99)⚠️</span>') print('<span style="color: red;font-weight: bold;">⚠ATTENTION: Signal Quality indicates no signal (99)⚠️</span>')
print("TRY TO RECONNECT:") print("TRY TO RECONNECT:")
command = f'AT+COPS=1,2,"{selected_networkID}"\r' command = f'AT+COPS=1,2,{selected_networkID}\r'
#command = f'AT+COPS=0\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
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>")
@@ -466,7 +489,7 @@ try:
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:
@@ -504,7 +527,8 @@ try:
# and reset HTTP profile (AT+UHTTP=0) -> ne fonctionne pas.. # and reset HTTP profile (AT+UHTTP=0) -> ne fonctionne pas..
# tester un reset avec CFUN 15 # tester un reset avec CFUN 15
# 1.Reconnexion au réseau (AT+COPS) # 1.Reconnexion au réseau (AT+COPS)
command = f'AT+COPS=1,2,"{selected_networkID}"\r' command = f'AT+COPS=1,2,{selected_networkID}\r'
#command = f'AT+COPS=0\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
responseReconnect = read_complete_response(ser_sara) responseReconnect = read_complete_response(ser_sara)
print("Response reconnect:") print("Response reconnect:")
@@ -545,7 +569,7 @@ 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="")
''' '''
+UHTTPER: profile_id,error_class,error_code +UHTTPER: profile_id,error_class,error_code
@@ -578,7 +602,7 @@ try:
responseResetHTTP2_profile = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5, wait_for_lines=["OK", "+CME ERROR"], debug=True) responseResetHTTP2_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(responseResetHTTP2_profile) print(responseResetHTTP2_profile)
print("</p>") print("</p>", end="")
# 2.2 code 1 (HHTP succeded) # 2.2 code 1 (HHTP succeded)
@@ -595,10 +619,78 @@ try:
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 10 seconds
# and update the RTC clock
if time_diff > 10:
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 10 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" ou "Operation not allowed"
else: else:
print('<span style="color: red;font-weight: bold;">No UUHTTPCR response</span>') print('<span style="color: red;font-weight: bold;">No UUHTTPCR response</span>')
print("Blink red LED") print("Blink red LED")
@@ -621,13 +713,13 @@ try:
print('<span style="color: orange;font-weight: bold;">📞Try reconnect to network📞</span>') print('<span style="color: orange;font-weight: bold;">📞Try reconnect to network📞</span>')
#IMPORTANT! #IMPORTANT!
# Reconnexion au réseau (AT+COPS) # Reconnexion au réseau (AT+COPS)
#command = f'AT+COPS=1,2,{selected_networkID}\r' command = f'AT+COPS=1,2,{selected_networkID}\r'
command = f'AT+COPS=0\r' #command = f'AT+COPS=0\r'
ser_sara.write(command.encode('utf-8')) ser_sara.write(command.encode('utf-8'))
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>')
@@ -636,7 +728,14 @@ 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="")
check_lines = responseResetHTTP_profile.strip().splitlines()
for line in check_lines:
if "+CME ERROR: Operation not allowed" in line:
print('<span style="color: red;font-weight: bold;">⚠ATTENTION: CME ERROR⚠</span>')
print('<span style="color: orange;font-weight: bold;">❓Try Reboot the module❓</span>')
#5. empty json #5. empty json
print("Empty SARA memory:") print("Empty SARA memory:")
@@ -678,7 +777,7 @@ try:
print('<p class="text-danger-emphasis">') print('<p class="text-danger-emphasis">')
print(response_SARA_8) print(response_SARA_8)
print("</p>") print("</p>", end="")
# si on recoit la réponse UHTTPCR # si on recoit la réponse UHTTPCR
if "+UUHTTPCR" in response_SARA_8: if "+UUHTTPCR" in response_SARA_8:
@@ -731,7 +830,7 @@ try:
response_SARA_9b = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False) response_SARA_9b = 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_9b) print(response_SARA_9b)
print("</p>") print("</p>", end="")
''' '''
+UHTTPER: profile_id,error_class,error_code +UHTTPER: profile_id,error_class,error_code
@@ -764,7 +863,7 @@ try:
response_SARA_4b = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False) response_SARA_4b = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
print('<p class="text-success">') print('<p class="text-success">')
print(response_SARA_4b) print(response_SARA_4b)
print('</p>') print("</p>", end="")