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