update
This commit is contained in:
@@ -6,7 +6,7 @@ Version Pro du ModuleAir avec CM4, SaraR4 et ecran Matrix LED p2 64x64.
|
|||||||
```
|
```
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install git gh apache2 sqlite3 php php-sqlite3 libsqlite3-dev python3 python3-pip jq g++ autossh i2c-tools python3-smbus -y
|
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 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 mkdir /var/www/moduleair_pro_4g/logs
|
||||||
sudo touch /var/www/moduleair_pro_4g/logs/app.log /var/www/moduleair_pro_4g/logs/loop.log /var/www/moduleair_pro_4g/matrix/input_NPM.txt /var/www/moduleair_pro_4g/matrix/input_MHZ16.txt /var/www/moduleair_pro_4g/wifi_list.csv
|
sudo touch /var/www/moduleair_pro_4g/logs/app.log /var/www/moduleair_pro_4g/logs/loop.log /var/www/moduleair_pro_4g/matrix/input_NPM.txt /var/www/moduleair_pro_4g/matrix/input_MHZ16.txt /var/www/moduleair_pro_4g/wifi_list.csv
|
||||||
|
|||||||
81
SARA/sara.py
81
SARA/sara.py
@@ -28,51 +28,58 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
|||||||
command = parameter[1] # ex: AT+CCID?
|
command = parameter[1] # ex: AT+CCID?
|
||||||
timeout = float(parameter[2]) # ex:2
|
timeout = float(parameter[2]) # ex:2
|
||||||
|
|
||||||
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:
|
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
|
# Read lines until a timeout occurs
|
||||||
response_lines = []
|
response_lines = []
|
||||||
while True:
|
start_time = time.time()
|
||||||
line = ser.readline().decode('utf-8').strip()
|
|
||||||
if not line:
|
while (time.time() - start_time) < timeout:
|
||||||
break # Break the loop if an empty line is encountered
|
line = ser.readline().decode('utf-8', errors='ignore').strip()
|
||||||
response_lines.append(line)
|
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
|
# Print the response
|
||||||
for line in response_lines:
|
for line in response_lines:
|
||||||
print(line)
|
print(line)
|
||||||
|
|
||||||
except serial.SerialException as e:
|
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:
|
finally:
|
||||||
if ser.is_open:
|
# Close the serial port if it's open
|
||||||
|
if 'ser' in locals() and ser.is_open:
|
||||||
ser.close()
|
ser.close()
|
||||||
#print("Serial closed")
|
|
||||||
|
|
||||||
|
|||||||
@@ -92,11 +92,13 @@ import json
|
|||||||
import serial
|
import serial
|
||||||
import time
|
import time
|
||||||
import busio
|
import busio
|
||||||
|
import requests
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
import sys
|
import sys
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
import RPi.GPIO as GPIO
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -130,7 +132,6 @@ conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#get config data from SQLite table
|
#get config data from SQLite table
|
||||||
def load_config_sqlite():
|
def load_config_sqlite():
|
||||||
"""
|
"""
|
||||||
@@ -235,6 +236,9 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
|||||||
'''
|
'''
|
||||||
Fonction très importante !!!
|
Fonction très importante !!!
|
||||||
Reads the complete response from a serial connection and waits for specific lines.
|
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:
|
if wait_for_lines is None:
|
||||||
wait_for_lines = [] # Default to an empty list if not provided
|
wait_for_lines = [] # Default to an empty list if not provided
|
||||||
@@ -336,120 +340,115 @@ def send_error_notification(device_id, error_type, additional_info=None):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id):
|
def modem_hardware_reboot():
|
||||||
"""
|
"""
|
||||||
Performs a complete modem restart sequence:
|
Performs a hardware reboot using transistors connected to pin 16 and 20:
|
||||||
1. Reboots the modem using the appropriate command for its version
|
pin 16 set to SARA GND
|
||||||
2. Waits for the modem to restart
|
pin 20 set to SARA ON (not used)
|
||||||
3. Resets the HTTP profile
|
|
||||||
4. For SARA-R5, resets the PDP connection
|
|
||||||
|
|
||||||
Args:
|
LOW -> cut the current
|
||||||
modem_version (str): The modem version, e.g., 'SARA-R500' or 'SARA-R410'
|
HIGH -> current flow
|
||||||
aircarto_profile_id (int): The HTTP profile ID to reset
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if the complete sequence was successful, False otherwise
|
|
||||||
"""
|
"""
|
||||||
print('<span style="color: orange;font-weight: bold;">🔄 Complete SARA reboot and reinitialize sequence 🔄</span>')
|
print('<span style="color: orange;font-weight: bold;">🔄 Hardware SARA reboot 🔄</span>')
|
||||||
|
|
||||||
# Step 1: Reboot the modem - Integrated modem_software_reboot logic
|
SARA_power_GPIO = 16
|
||||||
print('<span style="color: orange;font-weight: bold;">🔄 Software SARA reboot! 🔄</span>')
|
SARA_ON_GPIO = 20
|
||||||
|
|
||||||
# Use different commands based on modem version
|
GPIO.setmode(GPIO.BCM) # Use BCM numbering
|
||||||
if 'R5' in modem_version: # For SARA-R5 series
|
GPIO.setup(SARA_power_GPIO, GPIO.OUT) # Set GPIO17 as an output
|
||||||
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'))
|
GPIO.output(SARA_power_GPIO, GPIO.LOW)
|
||||||
response = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR"], debug=True)
|
time.sleep(2)
|
||||||
|
GPIO.output(SARA_power_GPIO, GPIO.HIGH)
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
print('<p class="text-danger-emphasis">')
|
|
||||||
print(response)
|
|
||||||
print("</p>", end="")
|
|
||||||
|
|
||||||
# Check if reboot command was acknowledged
|
|
||||||
reboot_success = response is not None and "OK" in response
|
|
||||||
if not reboot_success:
|
|
||||||
print("⚠️ Modem reboot command failed")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Step 2: Wait for the modem to restart (adjust time as needed)
|
|
||||||
print("Waiting for modem to restart...")
|
|
||||||
time.sleep(15) # 15 seconds should be enough for most modems to restart
|
|
||||||
|
|
||||||
# Step 3: Check if modem is responsive after reboot
|
|
||||||
print("Checking if modem is responsive...")
|
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)
|
for attempt in range(5):
|
||||||
if response_check is None or "OK" not in response_check:
|
ser_sara.write(b'AT\r')
|
||||||
print("⚠️ Modem not responding after reboot")
|
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
|
return False
|
||||||
|
|
||||||
print("✅ Modem restarted successfully")
|
def reset_PSD_CSD_connection():
|
||||||
|
"""
|
||||||
# Step 4: Reset the HTTP Profile
|
Function that reset the PSD CSD connection for the SARA R5
|
||||||
print('<span style="color: orange;font-weight: bold;">🔧 Resetting the HTTP Profile</span>')
|
returns true or false
|
||||||
command = f'AT+UHTTP={aircarto_profile_id},1,"data.nebuleair.fr"\r'
|
"""
|
||||||
ser_sara.write(command.encode('utf-8'))
|
print("⚠️Reseting PDP connection ")
|
||||||
responseResetHTTP = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5,
|
|
||||||
wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
|
||||||
print('<p class="text-danger-emphasis">')
|
|
||||||
print(responseResetHTTP)
|
|
||||||
print("</p>", end="")
|
|
||||||
|
|
||||||
http_reset_success = responseResetHTTP is not None and "OK" in responseResetHTTP
|
|
||||||
if not http_reset_success:
|
|
||||||
print("⚠️ HTTP profile reset failed")
|
|
||||||
# Continue anyway, don't return False here
|
|
||||||
|
|
||||||
# Step 5: For SARA-R5, reset the PDP connection
|
|
||||||
pdp_reset_success = True
|
pdp_reset_success = True
|
||||||
if modem_version == "SARA-R500":
|
# Activate PDP context 1
|
||||||
print("⚠️ Need to reset PDP connection for SARA-R500")
|
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)
|
||||||
|
|
||||||
# Activate PDP context 1
|
# Set the PDP type
|
||||||
print('➡️ Activate PDP context 1')
|
print('➡️ Set the PDP type to IPv4 referring to the output of the +CGDCONT read command')
|
||||||
command = f'AT+CGACT=1,1\r'
|
command = f'AT+UPSD=0,0,0\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
response_pdp1 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
response_pdp2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||||
print(response_pdp1, end="")
|
print(response_pdp2, end="")
|
||||||
pdp_reset_success = pdp_reset_success and (response_pdp1 is not None and "OK" in response_pdp1)
|
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('<span style="color: orange;font-weight: bold;">🔧 Resetting AirCarto HTTP Profile</span>')
|
||||||
|
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)
|
time.sleep(1)
|
||||||
|
http_reset_success = response_SARA_5 is not None and "OK" in response_SARA_5
|
||||||
# Set the PDP type
|
if not http_reset_success:
|
||||||
print('➡️ Set the PDP type to IPv4 referring to the output of the +CGDCONT read command')
|
print("⚠️ AirCarto HTTP profile reset failed")
|
||||||
command = f'AT+UPSD=0,0,0\r'
|
elif profile_id ==1:
|
||||||
ser_sara.write(command.encode('utf-8'))
|
pass # TODO: implement handling for profile 1
|
||||||
response_pdp2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
else:
|
||||||
print(response_pdp2, end="")
|
print(f"❌ Unsupported profile ID: {profile_id}")
|
||||||
pdp_reset_success = pdp_reset_success and (response_pdp2 is not None and "OK" in response_pdp2)
|
http_reset_success = False
|
||||||
time.sleep(1)
|
return http_reset_success
|
||||||
|
|
||||||
# Profile #0 is mapped on CID=1
|
|
||||||
print('➡️ Profile #0 is mapped on CID=1.')
|
|
||||||
command = f'AT+UPSD=0,100,1\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_pdp3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
|
||||||
print(response_pdp3, end="")
|
|
||||||
pdp_reset_success = pdp_reset_success and (response_pdp3 is not None and "OK" in response_pdp3)
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
# Activate the PSD profile
|
|
||||||
print('➡️ Activate the PSD profile #0: the IPv4 address is already assigned by the network.')
|
|
||||||
command = f'AT+UPSDA=0,3\r'
|
|
||||||
ser_sara.write(command.encode('utf-8'))
|
|
||||||
response_pdp4 = read_complete_response(ser_sara, wait_for_lines=["OK", "+UUPSDA"])
|
|
||||||
print(response_pdp4, end="")
|
|
||||||
pdp_reset_success = pdp_reset_success and (response_pdp4 is not None and ("OK" in response_pdp4 or "+UUPSDA" in response_pdp4))
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
if not pdp_reset_success:
|
|
||||||
print("⚠️ PDP connection reset had some issues")
|
|
||||||
|
|
||||||
# Return overall success
|
|
||||||
return http_reset_success and pdp_reset_success
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
'''
|
'''
|
||||||
@@ -463,11 +462,10 @@ try:
|
|||||||
print('<h3>START LOOP</h3>')
|
print('<h3>START LOOP</h3>')
|
||||||
|
|
||||||
#Local timestamp
|
#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'
|
||||||
print(rtc_time_str)
|
print(f"➡️Getting local timestamp: {rtc_time_str}")
|
||||||
|
|
||||||
if rtc_time_str == 'not connected':
|
if rtc_time_str == 'not connected':
|
||||||
print("⛔ Atttention RTC module not connected⛔")
|
print("⛔ Atttention RTC module not connected⛔")
|
||||||
@@ -488,7 +486,7 @@ try:
|
|||||||
# 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"
|
rtc_status = "valid"
|
||||||
print(influx_timestamp)
|
#print(influx_timestamp)
|
||||||
|
|
||||||
#NEXTPM
|
#NEXTPM
|
||||||
print("➡️Getting NPM values (last 6 measures)")
|
print("➡️Getting NPM values (last 6 measures)")
|
||||||
@@ -591,7 +589,7 @@ try:
|
|||||||
payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NH3", "value": str(averages[2])})
|
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
|
# Getting the LTE Signal
|
||||||
print("➡️Getting LTE signal")
|
print("➡️Getting LTE signal")
|
||||||
@@ -608,22 +606,47 @@ try:
|
|||||||
|
|
||||||
#1. No answer at all form SARA
|
#1. No answer at all form SARA
|
||||||
if response2 is None or response2 == "":
|
if response2 is None or response2 == "":
|
||||||
print("No answer from SARA module")
|
print("⚠️ATTENTION: No answer from SARA module")
|
||||||
print('🛑STOP LOOP🛑')
|
print('🛑STOP LOOP🛑')
|
||||||
print("<hr>")
|
print("<hr>")
|
||||||
|
|
||||||
#Send notification (WIFI)
|
#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
|
#end loop
|
||||||
sys.exit()
|
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:
|
elif "+CME ERROR" in response2:
|
||||||
print(f"SARA module returned error: {response2}")
|
print(f"SARA module returned error: {response2}")
|
||||||
print("The CSQ command is not supported by this module or in its current state")
|
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("⚠️ATTENTION: SARA is connected over serial but CSQ command not supported")
|
||||||
print('🛑STOP LOOP🛑')
|
print('🛑STOP LOOP🛑')
|
||||||
|
print("<hr>")
|
||||||
|
|
||||||
|
#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("<hr>")
|
||||||
|
#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
|
#end loop
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
@@ -643,7 +666,7 @@ try:
|
|||||||
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'
|
||||||
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, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=True)
|
||||||
print('<p class="text-danger-emphasis">')
|
print('<p class="text-danger-emphasis">')
|
||||||
print(responseReconnect)
|
print(responseReconnect)
|
||||||
print("</p>", end="")
|
print("</p>", end="")
|
||||||
@@ -658,8 +681,14 @@ try:
|
|||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
SEND TO AIRCARTO
|
____ _____ _ _ ____ _ ___ ____ ____ _ ____ _____ ___
|
||||||
|
/ ___|| ____| \ | | _ \ / \ |_ _| _ \ / ___| / \ | _ \_ _/ _ \
|
||||||
|
\___ \| _| | \| | | | | / _ \ | || |_) | | / _ \ | |_) || || | | |
|
||||||
|
___) | |___| |\ | |_| | / ___ \ | || _ <| |___ / ___ \| _ < | || |_| |
|
||||||
|
|____/|_____|_| \_|____/ /_/ \_\___|_| \_\\____/_/ \_\_| \_\|_| \___/
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
print('<p class="fw-bold">➡️SEND TO AIRCARTO SERVERS</p>', end="")
|
||||||
# Write Data to saraR4
|
# Write Data to saraR4
|
||||||
# 1. Open sensordata_csv.json (with correct data size)
|
# 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)
|
csv_string = ','.join(str(value) if value is not None else '' for value in payload_csv)
|
||||||
@@ -667,14 +696,14 @@ try:
|
|||||||
print("Open JSON:")
|
print("Open JSON:")
|
||||||
command = f'AT+UDWNFILE="sensordata_csv.json",{size_of_string}\r'
|
command = f'AT+UDWNFILE="sensordata_csv.json",{size_of_string}\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=True)
|
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=False)
|
||||||
print(response_SARA_1)
|
print(response_SARA_1)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
#2. Write to shell
|
#2. Write to shell
|
||||||
print("Write data to memory:")
|
print("Write data to memory:")
|
||||||
ser_sara.write(csv_string.encode())
|
ser_sara.write(csv_string.encode())
|
||||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||||
print(response_SARA_2)
|
print(response_SARA_2)
|
||||||
|
|
||||||
#3. Send to endpoint (with device ID)
|
#3. Send to endpoint (with device ID)
|
||||||
@@ -750,7 +779,7 @@ try:
|
|||||||
|
|
||||||
|
|
||||||
# Get error code
|
# 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'
|
command = f'AT+UHTTPER={aircarto_profile_id}\r'
|
||||||
ser_sara.write(command.encode('utf-8'))
|
ser_sara.write(command.encode('utf-8'))
|
||||||
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)
|
||||||
@@ -760,16 +789,35 @@ try:
|
|||||||
|
|
||||||
# Extract just the error code
|
# Extract just the error code
|
||||||
error_code = extract_error_code(response_SARA_9)
|
error_code = extract_error_code(response_SARA_9)
|
||||||
|
|
||||||
if error_code is not None:
|
if error_code is not None:
|
||||||
# Display interpretation based on error code
|
# Display interpretation based on error code
|
||||||
if error_code == 0:
|
if error_code == 0:
|
||||||
print('<p class="text-success">No error detected</p>')
|
print('<p class="text-success">No error detected</p>')
|
||||||
elif error_code == 4:
|
elif error_code == 4:
|
||||||
print('<p class="text-danger">Error 4: Invalid server Hostname</p>')
|
print('<p class="text-danger">Error 4: Invalid server Hostname</p>')
|
||||||
|
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:
|
elif error_code == 11:
|
||||||
print('<p class="text-danger">Error 11: Server connection error</p>')
|
print('<p class="text-danger">Error 11: Server connection error</p>')
|
||||||
elif error_code == 22:
|
elif error_code == 22:
|
||||||
print('<p class="text-danger">⚠️Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️</p>')
|
print('<p class="text-danger">⚠️Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️</p>')
|
||||||
|
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('<p class="text-danger">Error 26: Connection timed out</p>')
|
||||||
|
send_error_notification(device_id, "UHTTPER (error n°26) -> Connection timed out")
|
||||||
|
elif error_code == 44:
|
||||||
|
print('<p class="text-danger">Error 44: Connection lost</p>')
|
||||||
|
send_error_notification(device_id, "UHTTPER (error n°44) -> Connection lost")
|
||||||
elif error_code == 73:
|
elif error_code == 73:
|
||||||
print('<p class="text-danger">Error 73: Secure socket connect error</p>')
|
print('<p class="text-danger">Error 73: Secure socket connect error</p>')
|
||||||
else:
|
else:
|
||||||
@@ -779,11 +827,11 @@ try:
|
|||||||
|
|
||||||
|
|
||||||
#Software Reboot
|
#Software Reboot
|
||||||
software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
#software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||||
if software_reboot_success:
|
#if software_reboot_success:
|
||||||
print("Modem successfully rebooted and reinitialized")
|
# print("Modem successfully rebooted and reinitialized")
|
||||||
else:
|
#else:
|
||||||
print("There were issues with the modem reboot/reinitialize process")
|
# print("There were issues with the modem reboot/reinitialize process")
|
||||||
|
|
||||||
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
|
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
|
||||||
else:
|
else:
|
||||||
@@ -912,11 +960,11 @@ try:
|
|||||||
send_error_notification(device_id, "sara_error")
|
send_error_notification(device_id, "sara_error")
|
||||||
|
|
||||||
#Software Reboot
|
#Software Reboot
|
||||||
software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
#software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||||
if software_reboot_success:
|
#if software_reboot_success:
|
||||||
print("Modem successfully rebooted and reinitialized")
|
# print("Modem successfully rebooted and reinitialized")
|
||||||
else:
|
#else:
|
||||||
print("There were issues with the modem reboot/reinitialize process")
|
# print("There were issues with the modem reboot/reinitialize process")
|
||||||
|
|
||||||
|
|
||||||
#5. empty json
|
#5. empty json
|
||||||
|
|||||||
BIN
matrix/imageScreen/image_split
Normal file
BIN
matrix/imageScreen/image_split
Normal file
Binary file not shown.
BIN
matrix/imageScreen/image_split_boot
Normal file
BIN
matrix/imageScreen/image_split_boot
Normal file
Binary file not shown.
252
matrix/imageScreen/image_split_boot.cc
Normal file
252
matrix/imageScreen/image_split_boot.cc
Normal file
@@ -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 <math.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <Magick++.h>
|
||||||
|
#include <magick/image.h>
|
||||||
|
|
||||||
|
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<Magick::Image>;
|
||||||
|
|
||||||
|
// 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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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 <image-filename>\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;
|
||||||
|
}
|
||||||
273
matrix/imageScreen/image_split_old.cc
Normal file
273
matrix/imageScreen/image_split_old.cc
Normal file
@@ -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 <math.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
#include <Magick++.h>
|
||||||
|
#include <magick/image.h>
|
||||||
|
|
||||||
|
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<Magick::Image>;
|
||||||
|
|
||||||
|
// 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<std::chrono::milliseconds>(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<std::chrono::milliseconds>(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 <image-filename>\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 <image-filename>\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;
|
||||||
|
}
|
||||||
92
services/setup_matrix_services.sh
Normal file
92
services/setup_matrix_services.sh
Normal file
@@ -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."
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# File: /var/www/moduleair_pro_4g/services/setup_services.sh
|
# 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:
|
# to install:
|
||||||
# sudo chmod +x /var/www/moduleair_pro_4g/services/setup_services.sh
|
# sudo chmod +x /var/www/moduleair_pro_4g/services/setup_services.sh
|
||||||
# sudo /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
|
WantedBy=timers.target
|
||||||
EOL
|
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
|
# Reload systemd to recognize new services
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
# Enable and start all timers
|
# Enable and start all timers
|
||||||
echo "Enabling and starting all services..."
|
echo "Enabling and starting all services..."
|
||||||
for service in npm envea sara bme280 co2 db-cleanup; do
|
for service in npm envea sara bme280 co2 db-cleanup matrix-display; do
|
||||||
systemctl enable moduleair-$service-data.timer
|
systemctl enable moduleair-$service-data.timer 2>/dev/null || systemctl enable moduleair-$service.timer
|
||||||
systemctl start moduleair-$service-data.timer
|
systemctl start moduleair-$service-data.timer 2>/dev/null || systemctl start moduleair-$service.timer
|
||||||
echo "Started moduleair-$service-data timer"
|
echo "Started moduleair-$service timer"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Checking status of all timers..."
|
echo "Checking status of all timers..."
|
||||||
systemctl list-timers | grep moduleair
|
systemctl list-timers | grep moduleair
|
||||||
|
|
||||||
echo "Setup complete. All moduleair services are now running."
|
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 " 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 " sudo journalctl -u moduleair-npm-data.service"
|
||||||
echo "To restart a specific timer:"
|
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-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"
|
||||||
116
sqlite/set_config_smart.py
Normal file
116
sqlite/set_config_smart.py
Normal file
@@ -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!")
|
||||||
Reference in New Issue
Block a user