diff --git a/README.md b/README.md
index e9069fe..e8be8ac 100755
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
```
sudo apt update
sudo apt install git gh apache2 sqlite3 php php-sqlite3 libsqlite3-dev 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 gpiozero adafruit-circuitpython-bme280 crcmod psutil --break-system-packages
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
diff --git a/SARA/sara.py b/SARA/sara.py
index cfc5030..4db1194 100755
--- a/SARA/sara.py
+++ b/SARA/sara.py
@@ -28,51 +28,58 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
command = parameter[1] # ex: AT+CCID?
timeout = float(parameter[2]) # ex:2
-ser = serial.Serial(
- port=port, #USB0 or ttyS0
- baudrate=115200, #115200 ou 9600
- parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
- stopbits=serial.STOPBITS_ONE,
- bytesize=serial.EIGHTBITS,
- timeout = timeout
-)
-
-ser.write((command + '\r').encode('utf-8'))
-
-#ser.write(b'ATI\r') #General Information
-#ser.write(b'AT+CCID?\r') #SIM card number
-#ser.write(b'AT+CPIN?\r') #Check the status of the SIM card
-#ser.write(b'AT+CIND?\r') #Indication state (last number is SIM detection: 0 no SIM detection, 1 SIM detected, 2 not available)
-#ser.write(b'AT+UGPIOR=?\r') #Reads the current value of the specified GPIO pin
-#ser.write(b'AT+UGPIOC?\r') #GPIO select configuration
-#ser.write(b'AT+COPS=?\r') #Check the network and cellular technology the modem is currently using
-#ser.write(b'AT+COPS=1,2,20801') #connext to orange
-#ser.write(b'AT+CFUN=?\r') #Selects/read the level of functionality
-#ser.write(b'AT+URAT=?\r') #Radio Access Technology
-#ser.write(b'AT+USIMSTAT?')
-#ser.write(b'AT+IPR=115200') #Check/Define baud rate
-#ser.write(b'AT+CMUX=?')
-
-
-
try:
+ ser = serial.Serial(
+ port=port, #USB0 or ttyS0
+ baudrate=115200, #115200 ou 9600
+ parity=serial.PARITY_NONE, #PARITY_NONE, PARITY_EVEN or PARITY_ODD
+ stopbits=serial.STOPBITS_ONE,
+ bytesize=serial.EIGHTBITS,
+ timeout = timeout
+ )
+
+ ser.write((command + '\r').encode('utf-8'))
+
+ #ser.write(b'ATI\r') #General Information
+ #ser.write(b'AT+CCID?\r') #SIM card number
+ #ser.write(b'AT+CPIN?\r') #Check the status of the SIM card
+ #ser.write(b'AT+CIND?\r') #Indication state (last number is SIM detection: 0 no SIM detection, 1 SIM detected, 2 not available)
+ #ser.write(b'AT+UGPIOR=?\r') #Reads the current value of the specified GPIO pin
+ #ser.write(b'AT+UGPIOC?\r') #GPIO select configuration
+ #ser.write(b'AT+COPS=?\r') #Check the network and cellular technology the modem is currently using
+ #ser.write(b'AT+COPS=1,2,20801') #connext to orange
+ #ser.write(b'AT+CFUN=?\r') #Selects/read the level of functionality
+ #ser.write(b'AT+URAT=?\r') #Radio Access Technology
+ #ser.write(b'AT+USIMSTAT?')
+ #ser.write(b'AT+IPR=115200') #Check/Define baud rate
+ #ser.write(b'AT+CMUX=?')
+
+
# 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)
+ start_time = time.time()
+
+ while (time.time() - start_time) < timeout:
+ line = ser.readline().decode('utf-8', errors='ignore').strip()
+ if line:
+ response_lines.append(line)
+
+ # Check if we received any data
+ if not response_lines:
+ print(f"ERROR: No response received from {port} after sending command: {command}")
+ sys.exit(1)
# Print the response
for line in response_lines:
print(line)
except serial.SerialException as e:
- print(f"Error: {e}")
-
+ print(f"ERROR: Serial communication error: {e}")
+ sys.exit(1)
+except Exception as e:
+ print(f"ERROR: Unexpected error: {e}")
+ sys.exit(1)
finally:
- if ser.is_open:
- ser.close()
- #print("Serial closed")
-
+ # Close the serial port if it's open
+ if 'ser' in locals() and ser.is_open:
+ ser.close()
\ No newline at end of file
diff --git a/loop/SARA_send_data_v2.py b/loop/SARA_send_data_v2.py
index 6d093b6..ac00d35 100755
--- a/loop/SARA_send_data_v2.py
+++ b/loop/SARA_send_data_v2.py
@@ -92,11 +92,13 @@ import json
import serial
import time
import busio
+import requests
import re
import os
import traceback
import sys
import sqlite3
+import RPi.GPIO as GPIO
from threading import Thread
from datetime import datetime
@@ -130,7 +132,6 @@ conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
-
#get config data from SQLite table
def load_config_sqlite():
"""
@@ -235,6 +236,9 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
'''
Fonction très importante !!!
Reads the complete response from a serial connection and waits for specific lines.
+ timeout -> temps d'attente de la réponse de la première ligne (assez rapide car le SARA répond direct avec la commande recue)
+ end_of_response_timeout -> le temps d'inactivité entre deux lignes imprimées (plus long dans certain cas: le SARA mouline avant de finir vraiment)
+ wait_for_lines -> si on rencontre la string la fonction s'arrete
'''
if wait_for_lines is None:
wait_for_lines = [] # Default to an empty list if not provided
@@ -336,121 +340,116 @@ def send_error_notification(device_id, error_type, additional_info=None):
return False
-def modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id):
+def modem_hardware_reboot():
"""
- 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
+ Performs a hardware reboot using transistors connected to pin 16 and 20:
+ pin 16 set to SARA GND
+ pin 20 set to SARA ON (not used)
+ LOW -> cut the current
+ HIGH -> current flow
+
+ """
+ print('🔄 Hardware SARA reboot 🔄')
+
+ SARA_power_GPIO = 16
+ SARA_ON_GPIO = 20
+
+ GPIO.setmode(GPIO.BCM) # Use BCM numbering
+ GPIO.setup(SARA_power_GPIO, GPIO.OUT) # Set GPIO17 as an output
+
+ GPIO.output(SARA_power_GPIO, GPIO.LOW)
+ time.sleep(2)
+ GPIO.output(SARA_power_GPIO, GPIO.HIGH)
+ time.sleep(2)
+
+ print("Checking if modem is responsive...")
+
+ for attempt in range(5):
+ ser_sara.write(b'AT\r')
+ response_check = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
+ if response_check and "OK" in response_check:
+ print("✅ Modem is responsive after reboot.")
+ return True
+ print(f"⏳ Waiting for modem... attempt {attempt + 1}")
+ time.sleep(2)
+ else:
+ print("❌ Modem not responding after reboot.")
+ return False
+
+def reset_PSD_CSD_connection():
+ """
+ Function that reset the PSD CSD connection for the SARA R5
+ returns true or false
+ """
+ print("⚠️Reseting PDP connection ")
+ pdp_reset_success = True
+ # 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 pdp_reset_success
+
+def reset_server_hostname(profile_id):
+ """
+ Function that reset server hostname (URL) connection for the SARA R5
+ returns true or false
+ """
+ print("⚠️Reseting Server Hostname connection ")
+ http_reset_success = False # Default fallback
+
+ if profile_id == 0:
+ print('🔧 Resetting AirCarto HTTP Profile')
+ command = f'AT+UHTTP={profile_id},1,"data.nebuleair.fr"\r'
+ ser_sara.write((command + '\r').encode('utf-8'))
+ response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
+ print(response_SARA_5)
+ time.sleep(1)
+ http_reset_success = response_SARA_5 is not None and "OK" in response_SARA_5
+ if not http_reset_success:
+ print("⚠️ AirCarto HTTP profile reset failed")
+ elif profile_id ==1:
+ pass # TODO: implement handling for profile 1
+ else:
+ print(f"❌ Unsupported profile ID: {profile_id}")
+ http_reset_success = False
+ return http_reset_success
+
try:
'''
_ ___ ___ ____
@@ -463,11 +462,10 @@ try:
print('START LOOP
')
#Local timestamp
- print("➡️Getting local timestamp")
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'
- print(rtc_time_str)
+ print(f"➡️Getting local timestamp: {rtc_time_str}")
if rtc_time_str == 'not connected':
print("⛔ Atttention RTC module not connected⛔")
@@ -488,7 +486,7 @@ try:
# 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)
+ #print(influx_timestamp)
#NEXTPM
print("➡️Getting NPM values (last 6 measures)")
@@ -591,7 +589,7 @@ try:
payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NH3", "value": str(averages[2])})
- print("Verify SARA R4 connection")
+ #print("Verify SARA R4 connection")
# Getting the LTE Signal
print("➡️Getting LTE signal")
@@ -608,22 +606,47 @@ try:
#1. No answer at all form SARA
if response2 is None or response2 == "":
- print("No answer from SARA module")
+ print("⚠️ATTENTION: No answer from SARA module")
print('🛑STOP LOOP🛑')
print("
")
#Send notification (WIFI)
- send_error_notification(device_id, "serial_error")
+ send_error_notification(device_id, "SERIAL ISSUE ->no answer from sara")
+ #Hardware Reboot
+ hardware_reboot_success = modem_hardware_reboot()
+ if hardware_reboot_success:
+ print("✅Modem successfully rebooted and reinitialized")
+ else:
+ print("⛔There were issues with the modem reboot/reinitialize process")
#end loop
sys.exit()
- #2. si on a une erreur
+ #2. si on a une reponse du SARA mais c'est 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🛑')
+ print("
")
+
+ #end loop
+ sys.exit()
+
+ #3. On peut avoir une erreur de type "Socket:bind: Treck error 222 : Invalid argument"
+ elif "Socket:bind: Treck error" in response2:
+ print(f"SARA module returned error: {response2}")
+ print("⚠️ATTENTION: low-level error from the Treck TCP/IP stack")
+ print('🛑STOP LOOP🛑')
+ print("
")
+ #Send notification (WIFI)
+ send_error_notification(device_id, "SERIAL ISSUE -> Treck TCP/IP stack error")
+ #hardware reboot
+ hardware_reboot_success = modem_hardware_reboot()
+ if hardware_reboot_success:
+ print("✅Modem successfully rebooted and reinitialized")
+ else:
+ print("⛔There were issues with the modem reboot/reinitialize process")
#end loop
sys.exit()
@@ -643,7 +666,7 @@ try:
print("TRY TO RECONNECT:")
command = f'AT+COPS=1,2,"{selected_networkID}"\r'
ser_sara.write(command.encode('utf-8'))
- responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20)
+ responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=True)
print('')
print(responseReconnect)
print("
", end="")
@@ -658,8 +681,14 @@ try:
'''
- SEND TO AIRCARTO
+ ____ _____ _ _ ____ _ ___ ____ ____ _ ____ _____ ___
+ / ___|| ____| \ | | _ \ / \ |_ _| _ \ / ___| / \ | _ \_ _/ _ \
+ \___ \| _| | \| | | | | / _ \ | || |_) | | / _ \ | |_) || || | | |
+ ___) | |___| |\ | |_| | / ___ \ | || _ <| |___ / ___ \| _ < | || |_| |
+ |____/|_____|_| \_|____/ /_/ \_\___|_| \_\\____/_/ \_\_| \_\|_| \___/
+
'''
+ print('➡️SEND TO AIRCARTO SERVERS
', end="")
# Write Data to saraR4
# 1. Open sensordata_csv.json (with correct data size)
csv_string = ','.join(str(value) if value is not None else '' for value in payload_csv)
@@ -667,14 +696,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=True)
+ response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=False)
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=True)
+ response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
print(response_SARA_2)
#3. Send to endpoint (with device ID)
@@ -750,7 +779,7 @@ try:
# Get error code
- print("Getting error code (11->Server connection error, 73->Secure socket connect error)")
+ print("Getting error code")
command = f'AT+UHTTPER={aircarto_profile_id}\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_9 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
@@ -760,16 +789,35 @@ try:
# 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
')
+ send_error_notification(device_id, "UHTTPER (error n°4) -> Invalid Server Hostname")
+ server_hostname_resets = reset_server_hostname(aircarto_profile_id)
+ if server_hostname_resets:
+ print("✅server hostname reset successfully")
+ else:
+ print("⛔There were issues with the modem server hostname reinitialize process")
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)⚠️
')
+ send_error_notification(device_id, "UHTTPER (error n°22) -> PSD or CSD connection not established")
+ psd_csd_resets = reset_PSD_CSD_connection()
+ if psd_csd_resets:
+ print("✅PSD CSD connection reset successfully")
+ else:
+ print("⛔There were issues with the modem CSD PSD reinitialize process")
+ elif error_code == 26:
+ print('Error 26: Connection timed out
')
+ send_error_notification(device_id, "UHTTPER (error n°26) -> Connection timed out")
+ elif error_code == 44:
+ print('Error 44: Connection lost
')
+ send_error_notification(device_id, "UHTTPER (error n°44) -> Connection lost")
elif error_code == 73:
print('Error 73: Secure socket connect error
')
else:
@@ -779,11 +827,11 @@ try:
#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")
+ #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")
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
else:
@@ -912,11 +960,11 @@ try:
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")
+ #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
diff --git a/matrix/imageScreen/image_split b/matrix/imageScreen/image_split
new file mode 100644
index 0000000..d2724a3
Binary files /dev/null and b/matrix/imageScreen/image_split differ
diff --git a/matrix/imageScreen/image_split_boot b/matrix/imageScreen/image_split_boot
new file mode 100644
index 0000000..ecd68f3
Binary files /dev/null and b/matrix/imageScreen/image_split_boot differ
diff --git a/matrix/imageScreen/image_split_boot.cc b/matrix/imageScreen/image_split_boot.cc
new file mode 100644
index 0000000..49890d4
--- /dev/null
+++ b/matrix/imageScreen/image_split_boot.cc
@@ -0,0 +1,252 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+//
+// Example how to display an image with split reveal animation - BOOT VERSION
+// Runs once at boot and exits
+//
+// This requires an external dependency, so install these first before you
+// can call `make image-example`
+// sudo apt-get update
+// sudo apt-get install libgraphicsmagick++-dev libwebp-dev -y
+
+/*
+pour compiler:
+g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot.cc -o /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot -lrgbmatrix `GraphicsMagick++-config --cppflags --libs`
+
+pour lancer:
+sudo /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
+*/
+
+#include "led-matrix.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using rgb_matrix::Canvas;
+using rgb_matrix::RGBMatrix;
+using rgb_matrix::FrameCanvas;
+
+// Make sure we can exit gracefully when Ctrl-C is pressed.
+volatile bool interrupt_received = false;
+static void InterruptHandler(int signo) {
+ interrupt_received = true;
+}
+
+using ImageVector = std::vector;
+
+// Given the filename, load the image and scale to the size of the matrix.
+static ImageVector LoadImageAndScaleImage(const char *filename,
+ int target_width,
+ int target_height) {
+ ImageVector result;
+
+ ImageVector frames;
+ try {
+ readImages(&frames, filename);
+ } catch (std::exception &e) {
+ if (e.what())
+ fprintf(stderr, "%s\n", e.what());
+ return result;
+ }
+
+ if (frames.empty()) {
+ fprintf(stderr, "No image found.");
+ return result;
+ }
+
+ // Animated images have partial frames that need to be put together
+ if (frames.size() > 1) {
+ Magick::coalesceImages(&result, frames.begin(), frames.end());
+ } else {
+ result.push_back(frames[0]); // just a single still image.
+ }
+
+ for (Magick::Image &image : result) {
+ image.scale(Magick::Geometry(target_width, target_height));
+ }
+
+ return result;
+}
+
+// Copy image with split reveal animation effect
+void CopyImageToCanvasWithSplitReveal(const Magick::Image &image, Canvas *canvas, float progress) {
+ const int matrix_height = canvas->height();
+ const int matrix_width = canvas->width();
+ const int half_height = matrix_height / 2;
+
+ // Calculate how many rows of each half to show based on progress (0.0 to 1.0)
+ int top_rows_to_show = (int)(half_height * progress);
+ int bottom_rows_to_show = (int)(half_height * progress);
+
+ // Clear the canvas first
+ canvas->Clear();
+
+ // Draw top half (sliding down from above)
+ for (int row = 0; row < top_rows_to_show && row < half_height; ++row) {
+ // Calculate source row in the original image
+ int src_row = row;
+ if (src_row < (int)image.rows()) {
+ for (size_t x = 0; x < image.columns() && x < matrix_width; ++x) {
+ const Magick::Color &c = image.pixelColor(x, src_row);
+ if (c.alphaQuantum() < 256) {
+ canvas->SetPixel(x, row,
+ ScaleQuantumToChar(c.redQuantum()),
+ ScaleQuantumToChar(c.greenQuantum()),
+ ScaleQuantumToChar(c.blueQuantum()));
+ }
+ }
+ }
+ }
+
+ // Draw bottom half (sliding up from below)
+ for (int row = 0; row < bottom_rows_to_show && row < half_height; ++row) {
+ // Calculate destination row (starting from bottom)
+ int dest_row = matrix_height - 1 - row;
+ // Calculate source row in the original image (from bottom half)
+ int src_row = (int)image.rows() - 1 - row;
+
+ if (src_row >= half_height && src_row < (int)image.rows() && dest_row >= half_height) {
+ for (size_t x = 0; x < image.columns() && x < matrix_width; ++x) {
+ const Magick::Color &c = image.pixelColor(x, src_row);
+ if (c.alphaQuantum() < 256) {
+ canvas->SetPixel(x, dest_row,
+ ScaleQuantumToChar(c.redQuantum()),
+ ScaleQuantumToChar(c.greenQuantum()),
+ ScaleQuantumToChar(c.blueQuantum()));
+ }
+ }
+ }
+ }
+}
+
+// Show image with split reveal animation - BOOT VERSION (runs once and exits)
+void ShowBootSplitRevealAnimation(const Magick::Image &image, RGBMatrix *matrix) {
+ const int animation_duration_ms = 3000; // 3 seconds animation
+ const int display_duration_ms = 5000; // 5 seconds display
+ const int fps = 60; // 60 frames per second for smooth animation
+ const int frame_delay_ms = 1000 / fps;
+ const int total_frames = animation_duration_ms / frame_delay_ms;
+
+ FrameCanvas *offscreen_canvas = matrix->CreateFrameCanvas();
+
+ printf("Starting boot split reveal animation...\n");
+
+ auto start_time = std::chrono::steady_clock::now();
+
+ // Animation phase
+ for (int frame = 0; frame <= total_frames && !interrupt_received; ++frame) {
+ // Calculate progress (0.0 to 1.0)
+ float progress = (float)frame / total_frames;
+
+ // Apply easing function for smoother animation (ease-out)
+ progress = 1.0f - (1.0f - progress) * (1.0f - progress);
+
+ // Render frame with current progress
+ CopyImageToCanvasWithSplitReveal(image, offscreen_canvas, progress);
+ offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas);
+
+ // Maintain consistent frame rate
+ std::this_thread::sleep_for(std::chrono::milliseconds(frame_delay_ms));
+ }
+
+ auto animation_end = std::chrono::steady_clock::now();
+ auto animation_duration = std::chrono::duration_cast(animation_end - start_time);
+ printf("Boot animation completed in %ld ms\n", animation_duration.count());
+
+ // Display final complete image
+ printf("Displaying boot image for %d seconds...\n", display_duration_ms / 1000);
+ auto display_start = std::chrono::steady_clock::now();
+
+ while (!interrupt_received) {
+ auto current_time = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast(current_time - display_start);
+
+ if (elapsed.count() >= display_duration_ms) {
+ break;
+ }
+
+ // Keep refreshing the display
+ offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas);
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+
+ auto total_end = std::chrono::steady_clock::now();
+ auto total_duration = std::chrono::duration_cast(total_end - start_time);
+ printf("Boot display completed. Total time: %ld ms (%ds animation + %ds display)\n",
+ total_duration.count(), animation_duration_ms / 1000, display_duration_ms / 1000);
+}
+
+int main(int argc, char *argv[]) {
+ Magick::InitializeMagick(*argv);
+
+ // Check if a filename was provided
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s \n", argv[0]);
+ return 1;
+ }
+ const char *filename = argv[1];
+
+ // Initialize with hardcoded defaults for your specific hardware
+ RGBMatrix::Options defaults;
+ defaults.hardware_mapping = "moduleair_pinout";
+ defaults.rows = 64;
+ defaults.cols = 128;
+ defaults.chain_length = 1;
+ defaults.parallel = 1;
+ defaults.row_address_type = 3;
+ defaults.show_refresh_rate = false; // Disable refresh rate display for boot
+ defaults.brightness = 100;
+ defaults.pwm_bits = 1;
+ defaults.panel_type = "FM6126A";
+ defaults.disable_hardware_pulsing = false;
+
+ // Set up runtime options
+ rgb_matrix::RuntimeOptions runtime_opt;
+ runtime_opt.gpio_slowdown = 4; // Adjust if needed for your Pi model
+ runtime_opt.daemon = 0;
+ runtime_opt.drop_privileges = 0; // Set to 1 if not running as root
+
+ // Handle Ctrl+C to exit gracefully
+ signal(SIGTERM, InterruptHandler);
+ signal(SIGINT, InterruptHandler);
+
+ // Create the matrix with our defaults
+ RGBMatrix *matrix = RGBMatrix::CreateFromOptions(defaults, runtime_opt);
+ if (matrix == NULL) {
+ fprintf(stderr, "Failed to create matrix\n");
+ return 1;
+ }
+
+ // Load and process the image
+ ImageVector images = LoadImageAndScaleImage(filename,
+ matrix->width(),
+ matrix->height());
+
+ // Display the image with split reveal animation (once only)
+ switch (images.size()) {
+ case 0: // failed to load image
+ fprintf(stderr, "Failed to load image\n");
+ break;
+ case 1: // single image - show with split reveal animation
+ ShowBootSplitRevealAnimation(images[0], matrix);
+ break;
+ default: // animated image - just show first frame with split reveal
+ printf("Animated image detected, using first frame for boot animation\n");
+ ShowBootSplitRevealAnimation(images[0], matrix);
+ break;
+ }
+
+ // Clean up and exit
+ printf("Boot animation finished. Cleaning up and exiting...\n");
+ matrix->Clear();
+ delete matrix;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/matrix/imageScreen/image_split_old.cc b/matrix/imageScreen/image_split_old.cc
new file mode 100644
index 0000000..9fe3794
--- /dev/null
+++ b/matrix/imageScreen/image_split_old.cc
@@ -0,0 +1,273 @@
+// -*- mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; -*-
+//
+// Example how to display an image with split reveal animation
+//
+// This requires an external dependency, so install these first before you
+// can call `make image-example`
+// sudo apt-get update
+// sudo apt-get install libgraphicsmagick++-dev libwebp-dev -y
+
+/*
+pour compiler:
+g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/imageScreen/image_split.cc -o /var/www/moduleair_pro_4g/matrix/imageScreen/image_split -lrgbmatrix `GraphicsMagick++-config --cppflags --libs`
+
+pour lancer:
+sudo /var/www/moduleair_pro_4g/matrix/imageScreen/image_split /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
+*/
+
+#include "led-matrix.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+using rgb_matrix::Canvas;
+using rgb_matrix::RGBMatrix;
+using rgb_matrix::FrameCanvas;
+
+// Make sure we can exit gracefully when Ctrl-C is pressed.
+volatile bool interrupt_received = false;
+static void InterruptHandler(int signo) {
+ interrupt_received = true;
+}
+
+using ImageVector = std::vector;
+
+// Given the filename, load the image and scale to the size of the matrix.
+static ImageVector LoadImageAndScaleImage(const char *filename,
+ int target_width,
+ int target_height) {
+ ImageVector result;
+
+ ImageVector frames;
+ try {
+ readImages(&frames, filename);
+ } catch (std::exception &e) {
+ if (e.what())
+ fprintf(stderr, "%s\n", e.what());
+ return result;
+ }
+
+ if (frames.empty()) {
+ fprintf(stderr, "No image found.");
+ return result;
+ }
+
+ // Animated images have partial frames that need to be put together
+ if (frames.size() > 1) {
+ Magick::coalesceImages(&result, frames.begin(), frames.end());
+ } else {
+ result.push_back(frames[0]); // just a single still image.
+ }
+
+ for (Magick::Image &image : result) {
+ image.scale(Magick::Geometry(target_width, target_height));
+ }
+
+ return result;
+}
+
+// Copy image with split reveal animation effect
+void CopyImageToCanvasWithSplitReveal(const Magick::Image &image, Canvas *canvas, float progress) {
+ const int matrix_height = canvas->height();
+ const int matrix_width = canvas->width();
+ const int half_height = matrix_height / 2;
+
+ // Calculate how many rows of each half to show based on progress (0.0 to 1.0)
+ int top_rows_to_show = (int)(half_height * progress);
+ int bottom_rows_to_show = (int)(half_height * progress);
+
+ // Clear the canvas first
+ canvas->Clear();
+
+ // Draw top half (sliding down from above)
+ for (int row = 0; row < top_rows_to_show && row < half_height; ++row) {
+ // Calculate source row in the original image
+ int src_row = row;
+ if (src_row < (int)image.rows()) {
+ for (size_t x = 0; x < image.columns() && x < matrix_width; ++x) {
+ const Magick::Color &c = image.pixelColor(x, src_row);
+ if (c.alphaQuantum() < 256) {
+ canvas->SetPixel(x, row,
+ ScaleQuantumToChar(c.redQuantum()),
+ ScaleQuantumToChar(c.greenQuantum()),
+ ScaleQuantumToChar(c.blueQuantum()));
+ }
+ }
+ }
+ }
+
+ // Draw bottom half (sliding up from below)
+ for (int row = 0; row < bottom_rows_to_show && row < half_height; ++row) {
+ // Calculate destination row (starting from bottom)
+ int dest_row = matrix_height - 1 - row;
+ // Calculate source row in the original image (from bottom half)
+ int src_row = (int)image.rows() - 1 - row;
+
+ if (src_row >= half_height && src_row < (int)image.rows() && dest_row >= half_height) {
+ for (size_t x = 0; x < image.columns() && x < matrix_width; ++x) {
+ const Magick::Color &c = image.pixelColor(x, src_row);
+ if (c.alphaQuantum() < 256) {
+ canvas->SetPixel(x, dest_row,
+ ScaleQuantumToChar(c.redQuantum()),
+ ScaleQuantumToChar(c.greenQuantum()),
+ ScaleQuantumToChar(c.blueQuantum()));
+ }
+ }
+ }
+ }
+}
+
+// Show image with split reveal animation
+void ShowSplitRevealAnimation(const Magick::Image &image, RGBMatrix *matrix) {
+ const int animation_duration_ms = 3000; // 3 seconds total
+ const int fps = 60; // 60 frames per second for smooth animation
+ const int frame_delay_ms = 1000 / fps;
+ const int total_frames = animation_duration_ms / frame_delay_ms;
+
+ FrameCanvas *offscreen_canvas = matrix->CreateFrameCanvas();
+
+ printf("Starting split reveal animation (3 seconds)...\n");
+
+ auto start_time = std::chrono::steady_clock::now();
+
+ for (int frame = 0; frame <= total_frames && !interrupt_received; ++frame) {
+ // Calculate progress (0.0 to 1.0)
+ float progress = (float)frame / total_frames;
+
+ // Apply easing function for smoother animation (ease-out)
+ progress = 1.0f - (1.0f - progress) * (1.0f - progress);
+
+ // Render frame with current progress
+ CopyImageToCanvasWithSplitReveal(image, offscreen_canvas, progress);
+ offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas);
+
+ // Maintain consistent frame rate
+ std::this_thread::sleep_for(std::chrono::milliseconds(frame_delay_ms));
+
+ // Debug output every 30 frames (approximately every 0.5 seconds)
+ if (frame % 30 == 0) {
+ printf("Animation progress: %.1f%%\n", progress * 100);
+ }
+ }
+
+ auto end_time = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast(end_time - start_time);
+ printf("Animation completed in %ld ms\n", duration.count());
+
+ // Show final complete image for 3 seconds
+ printf("Displaying full image for 3 seconds...\n");
+ auto display_start = std::chrono::steady_clock::now();
+ const int display_duration_ms = 3000; // 3 seconds
+
+ while (!interrupt_received) {
+ auto current_time = std::chrono::steady_clock::now();
+ auto elapsed = std::chrono::duration_cast(current_time - display_start);
+
+ if (elapsed.count() >= display_duration_ms) {
+ break;
+ }
+
+ // Keep refreshing the display
+ offscreen_canvas = matrix->SwapOnVSync(offscreen_canvas);
+ std::this_thread::sleep_for(std::chrono::milliseconds(50));
+ }
+
+ printf("Display completed. Total time: 6 seconds (3s animation + 3s display)\n");
+}
+
+// Regular copy function for animated images
+void CopyImageToCanvas(const Magick::Image &image, Canvas *canvas) {
+ const int offset_x = 0, offset_y = 0;
+ for (size_t y = 0; y < image.rows(); ++y) {
+ for (size_t x = 0; x < image.columns(); ++x) {
+ const Magick::Color &c = image.pixelColor(x, y);
+ if (c.alphaQuantum() < 256) {
+ canvas->SetPixel(x + offset_x, y + offset_y,
+ ScaleQuantumToChar(c.redQuantum()),
+ ScaleQuantumToChar(c.greenQuantum()),
+ ScaleQuantumToChar(c.blueQuantum()));
+ }
+ }
+ }
+}
+
+int usage(const char *progname) {
+ fprintf(stderr, "Usage: %s \n", progname);
+ return 1;
+}
+
+int main(int argc, char *argv[]) {
+ Magick::InitializeMagick(*argv);
+
+ // Check if a filename was provided
+ if (argc != 2) {
+ fprintf(stderr, "Usage: %s \n", argv[0]);
+ return 1;
+ }
+ const char *filename = argv[1];
+
+ // Initialize with hardcoded defaults for your specific hardware
+ RGBMatrix::Options defaults;
+ defaults.hardware_mapping = "moduleair_pinout";
+ defaults.rows = 64;
+ defaults.cols = 128;
+ defaults.chain_length = 1;
+ defaults.parallel = 1;
+ defaults.row_address_type = 3;
+ defaults.show_refresh_rate = true;
+ defaults.brightness = 100;
+ defaults.pwm_bits = 1;
+ defaults.panel_type = "FM6126A";
+ defaults.disable_hardware_pulsing = false;
+
+ // Set up runtime options
+ rgb_matrix::RuntimeOptions runtime_opt;
+ runtime_opt.gpio_slowdown = 4; // Adjust if needed for your Pi model
+ runtime_opt.daemon = 0;
+ runtime_opt.drop_privileges = 0; // Set to 1 if not running as root
+
+ // Handle Ctrl+C to exit gracefully
+ signal(SIGTERM, InterruptHandler);
+ signal(SIGINT, InterruptHandler);
+
+ // Create the matrix with our defaults
+ RGBMatrix *matrix = RGBMatrix::CreateFromOptions(defaults, runtime_opt);
+ if (matrix == NULL) {
+ fprintf(stderr, "Failed to create matrix\n");
+ return 1;
+ }
+
+ // Load and process the image
+ ImageVector images = LoadImageAndScaleImage(filename,
+ matrix->width(),
+ matrix->height());
+
+ // Display the image with split reveal animation
+ switch (images.size()) {
+ case 0: // failed to load image
+ fprintf(stderr, "Failed to load image\n");
+ break;
+ case 1: // single image - show with split reveal animation
+ ShowSplitRevealAnimation(images[0], matrix);
+ break;
+ default: // animated image - just show first frame with split reveal
+ printf("Animated image detected, using first frame for split reveal animation\n");
+ ShowSplitRevealAnimation(images[0], matrix);
+ break;
+ }
+
+ // Clean up and exit
+ printf("Animation finished. Cleaning up and exiting...\n");
+ matrix->Clear();
+ delete matrix;
+
+ return 0;
+}
\ No newline at end of file
diff --git a/services/setup_matrix_services.sh b/services/setup_matrix_services.sh
new file mode 100644
index 0000000..b1b926d
--- /dev/null
+++ b/services/setup_matrix_services.sh
@@ -0,0 +1,92 @@
+#!/bin/bash
+# File: /var/www/moduleair_pro_4g/services/setup_matrix_services.sh
+# Purpose: Set up LED matrix boot display service (runs once at boot)
+# to install:
+# sudo chmod +x /var/www/moduleair_pro_4g/services/setup_matrix_services.sh
+# sudo /var/www/moduleair_pro_4g/services/setup_matrix_services.sh
+
+echo "Setting up moduleair boot display service..."
+
+# Create directory for logs if it doesn't exist
+mkdir -p /var/www/moduleair_pro_4g/logs
+
+
+# Create service file for LED Matrix Boot Display (one-shot at boot)
+cat > /etc/systemd/system/moduleair-matrix-boot.service << 'EOL'
+[Unit]
+Description=moduleair LED Matrix Boot Display Service (runs once)
+After=network.target
+After=multi-user.target
+
+[Service]
+Type=oneshot
+ExecStart=/var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
+User=root
+WorkingDirectory=/var/www/moduleair_pro_4g/matrix/imageScreen/
+StandardOutput=append:/var/www/moduleair_pro_4g/logs/matrix_boot_service.log
+StandardError=append:/var/www/moduleair_pro_4g/logs/matrix_boot_service_errors.log
+TimeoutStartSec=30s
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
+EOL
+
+# Compile the boot version of the program
+echo "Compiling boot version of image_split..."
+cd /var/www/moduleair_pro_4g/matrix/imageScreen/
+
+# Make sure we have the boot version source file
+if [ ! -f "image_split_boot.cc" ]; then
+ echo "Creating image_split_boot.cc from provided code..."
+ # The code will be created separately as image_split_boot.cc
+ echo "Please save the boot version code as image_split_boot.cc first"
+ exit 1
+fi
+
+# Compile the boot version
+g++ -I/var/www/moduleair_pro_4g/matrix/include -L/var/www/moduleair_pro_4g/matrix/lib \
+ /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot.cc \
+ -o /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot \
+ -lrgbmatrix `GraphicsMagick++-config --cppflags --libs`
+
+# Check if compilation was successful
+if [ ! -f "image_split_boot" ]; then
+ echo "Compilation failed! Please check dependencies and try again."
+ exit 1
+fi
+
+# Make sure the matrix executable has proper permissions
+echo "Setting permissions for LED matrix boot executable..."
+chmod +x /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot
+
+# Reload systemd to recognize new service
+systemctl daemon-reload
+
+# Enable the boot service
+echo "Enabling boot display service..."
+systemctl enable moduleair-matrix-boot.service
+
+echo "Setup complete!"
+echo ""
+echo "LED Matrix Boot Display will:"
+echo " - Run once at boot after network and multi-user target"
+echo " - Show 3-second split reveal animation"
+echo " - Display image for 5 seconds"
+echo " - Exit automatically (total ~8 seconds)"
+echo ""
+echo "To test the service manually:"
+echo " sudo systemctl start moduleair-matrix-boot.service"
+echo ""
+echo "To check the status:"
+echo " sudo systemctl status moduleair-matrix-boot.service"
+echo ""
+echo "To view logs:"
+echo " sudo journalctl -u moduleair-matrix-boot.service"
+echo " tail -f /var/www/moduleair_pro_4g/logs/matrix_boot_service.log"
+echo ""
+echo "To disable boot display:"
+echo " sudo systemctl disable moduleair-matrix-boot.service"
+echo ""
+echo "Note: The old repeating matrix display timer has been disabled."
+echo "If you want to re-enable it, run the original setup_services.sh script."
\ No newline at end of file
diff --git a/services/setup_services.sh b/services/setup_services.sh
index 36c7c15..f0d9d1a 100644
--- a/services/setup_services.sh
+++ b/services/setup_services.sh
@@ -1,6 +1,6 @@
#!/bin/bash
# File: /var/www/moduleair_pro_4g/services/setup_services.sh
-# Purpose: Set up all systemd services for moduleair data collection
+# Purpose: Set up all systemd services for moduleair data collection and LED matrix display
# to install:
# sudo chmod +x /var/www/moduleair_pro_4g/services/setup_services.sh
# sudo /var/www/moduleair_pro_4g/services/setup_services.sh
@@ -205,24 +205,76 @@ AccuracySec=1h
WantedBy=timers.target
EOL
+# Create service and timer files for LED Matrix Display
+cat > /etc/systemd/system/moduleair-matrix-display.service << 'EOL'
+[Unit]
+Description=moduleair LED Matrix Display Service
+After=network.target
+
+[Service]
+Type=oneshot
+ExecStart=/var/www/moduleair_pro_4g/matrix/imageScreen/image_split /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
+User=root
+WorkingDirectory=/var/www/moduleair_pro_4g/matrix/imageScreen/
+StandardOutput=append:/var/www/moduleair_pro_4g/logs/matrix_service.log
+StandardError=append:/var/www/moduleair_pro_4g/logs/matrix_service_errors.log
+
+[Install]
+WantedBy=multi-user.target
+EOL
+
+cat > /etc/systemd/system/moduleair-matrix-display.timer << 'EOL'
+[Unit]
+Description=Run moduleair LED Matrix Display every 10 seconds
+Requires=moduleair-matrix-display.service
+
+[Timer]
+OnBootSec=30s
+OnUnitActiveSec=10s
+AccuracySec=1s
+
+[Install]
+WantedBy=timers.target
+EOL
+
+# Make sure the matrix executable has proper permissions
+echo "Setting permissions for LED matrix executable..."
+chmod +x /var/www/moduleair_pro_4g/matrix/imageScreen/image_split
+
# Reload systemd to recognize new services
systemctl daemon-reload
# Enable and start all timers
echo "Enabling and starting all services..."
-for service in npm envea sara bme280 co2 db-cleanup; do
- systemctl enable moduleair-$service-data.timer
- systemctl start moduleair-$service-data.timer
- echo "Started moduleair-$service-data timer"
+for service in npm envea sara bme280 co2 db-cleanup matrix-display; do
+ systemctl enable moduleair-$service-data.timer 2>/dev/null || systemctl enable moduleair-$service.timer
+ systemctl start moduleair-$service-data.timer 2>/dev/null || systemctl start moduleair-$service.timer
+ echo "Started moduleair-$service timer"
done
echo "Checking status of all timers..."
systemctl list-timers | grep moduleair
echo "Setup complete. All moduleair services are now running."
-echo "To check the status of a specific service:"
+echo ""
+echo "LED Matrix Display will:"
+echo " - Start 30 seconds after boot"
+echo " - Run every 10 seconds (6s animation + 4s gap)"
+echo " - Show split reveal animation + 3s display"
+echo ""
+echo "To check the status of services:"
echo " sudo systemctl status moduleair-npm-data.service"
-echo "To view logs for a specific service:"
+echo " sudo systemctl status moduleair-matrix-display.service"
+echo ""
+echo "To view logs for services:"
echo " sudo journalctl -u moduleair-npm-data.service"
-echo "To restart a specific timer:"
-echo " sudo systemctl restart moduleair-npm-data.timer"
\ No newline at end of file
+echo " sudo journalctl -u moduleair-matrix-display.service"
+echo " tail -f /var/www/moduleair_pro_4g/logs/matrix_service.log"
+echo ""
+echo "To restart timers:"
+echo " sudo systemctl restart moduleair-npm-data.timer"
+echo " sudo systemctl restart moduleair-matrix-display.timer"
+echo ""
+echo "To disable matrix display:"
+echo " sudo systemctl stop moduleair-matrix-display.timer"
+echo " sudo systemctl disable moduleair-matrix-display.timer"
\ No newline at end of file
diff --git a/sqlite/set_config_smart.py b/sqlite/set_config_smart.py
new file mode 100644
index 0000000..cd874ca
--- /dev/null
+++ b/sqlite/set_config_smart.py
@@ -0,0 +1,116 @@
+'''
+ ____ ___ _ _ _
+ / ___| / _ \| | (_) |_ ___
+ \___ \| | | | | | | __/ _ \
+ ___) | |_| | |___| | || __/
+ |____/ \__\_\_____|_|\__\___|
+
+Script to set the config (smart version - doesn't overwrite existing data)
+/usr/bin/python3 /var/www/moduleair_pro_4g/sqlite/set_config_smart.py
+
+in case of readonly error:
+sudo chmod 777 /var/www/moduleair_pro_4g/sqlite/sensors.db
+'''
+
+import sqlite3
+
+# Connect to (or create if not existent) the database
+conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
+cursor = conn.cursor()
+
+print(f"Connected to database")
+
+def safe_insert_config(key, value, value_type):
+ """Insert config only if it doesn't exist"""
+ cursor.execute("SELECT COUNT(*) FROM config_table WHERE key = ?", (key,))
+ if cursor.fetchone()[0] == 0:
+ cursor.execute(
+ "INSERT INTO config_table (key, value, type) VALUES (?, ?, ?)",
+ (key, value, value_type)
+ )
+ print(f"Added config: {key} = {value}")
+ else:
+ print(f"Config already exists: {key}")
+
+def safe_insert_envea_sonde(connected, port, name, coefficient):
+ """Insert envea sonde only if it doesn't exist (check by port and name)"""
+ cursor.execute("SELECT COUNT(*) FROM envea_sondes_table WHERE port = ? AND name = ?", (port, name))
+ if cursor.fetchone()[0] == 0:
+ cursor.execute(
+ "INSERT INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
+ (1 if connected else 0, port, name, coefficient)
+ )
+ print(f"Added envea sonde: {name} on {port}")
+ else:
+ print(f"Envea sonde already exists: {name} on {port}")
+
+# Check if tables are empty (first run)
+cursor.execute("SELECT COUNT(*) FROM config_table")
+config_count = cursor.fetchone()[0]
+
+cursor.execute("SELECT COUNT(*) FROM config_scripts_table")
+scripts_count = cursor.fetchone()[0]
+
+cursor.execute("SELECT COUNT(*) FROM envea_sondes_table")
+sondes_count = cursor.fetchone()[0]
+
+# If this is the first run (all tables empty), clear and do fresh install
+if config_count == 0 and scripts_count == 0 and sondes_count == 0:
+ print("First run detected - setting up initial configuration...")
+ # Clear existing data (if any)
+ cursor.execute("DELETE FROM config_table")
+ cursor.execute("DELETE FROM config_scripts_table")
+ cursor.execute("DELETE FROM envea_sondes_table")
+ print("Tables cleared for fresh setup")
+else:
+ print("Existing configuration detected - will only add missing entries...")
+
+
+# Insert general configurations
+config_entries = [
+ ("modem_config_mode", "0", "bool"),
+ ("deviceID", "XXXX", "str"),
+ ("npm_5channel", "0", "bool"),
+ ("latitude_raw", "0", "int"),
+ ("longitude_raw", "0", "int"),
+ ("latitude_precision", "0", "int"),
+ ("longitude_precision", "0", "int"),
+ ("deviceName", "ModuleAir-proXXX", "str"),
+ ("SaraR4_baudrate", "115200", "int"),
+ ("NPM_solo_port", "/dev/ttyAMA5", "str"),
+ ("sshTunnel_port", "59228", "int"),
+ ("SARA_R4_general_status", "connected", "str"),
+ ("SARA_R4_SIM_status", "connected", "str"),
+ ("SARA_R4_network_status", "connected", "str"),
+ ("SARA_R4_neworkID", "20810", "int"),
+ ("WIFI_status", "connected", "str"),
+ ("send_uSpot", "0", "bool"),
+ ("windMeter", "0", "bool"),
+ ("modem_version", "XXX", "str"),
+ # Add new config entries here
+ ("matrix_display", "enabled", "str"),
+ ("matrix_display_type", "split_reveal", "str"),
+ ("matrix_brightness", "100", "int")
+]
+
+print("\nProcessing general configurations...")
+for key, value, value_type in config_entries:
+ safe_insert_config(key, value, value_type)
+
+# Insert envea sondes
+envea_sondes = [
+ (False, "ttyAMA4", "h2s", 4),
+ (False, "ttyAMA3", "no2", 1),
+ (False, "ttyAMA2", "o3", 1),
+ # Add new sondes here if needed
+]
+
+print("\nProcessing envea sondes...")
+for connected, port, name, coefficient in envea_sondes:
+ safe_insert_envea_sonde(connected, port, name, coefficient)
+
+# Commit and close the connection
+conn.commit()
+conn.close()
+
+print("\nDatabase updated successfully!")
\ No newline at end of file