66 Commits

Author SHA1 Message Date
Your Name
020594e065 updates 2025-05-23 15:09:22 +02:00
PaulVua
5a1a4e0d81 updates 2025-05-23 14:38:32 +02:00
PaulVua
3cd5b13c25 updates 2025-05-23 14:31:23 +02:00
PaulVua
5a0f1c0745 updates 2025-05-23 14:30:18 +02:00
PaulVua
2516a3bd1c updates 2025-05-23 14:08:21 +02:00
PaulVua
1b8dc54fe0 updates 2025-05-23 14:03:57 +02:00
PaulVua
2bd74ca91a updates 2025-05-23 11:02:06 +02:00
PaulVua
f40c105abf updates 2025-05-23 10:48:41 +02:00
Your Name
fdef8e2df0 update 2025-05-20 11:57:55 +02:00
Your Name
386ad6fb03 update 2025-05-20 11:24:38 +02:00
Your Name
a7c138e93f update 2025-05-20 11:23:43 +02:00
Your Name
4e4832b128 update 2025-05-20 10:43:27 +02:00
Your Name
11463b175c update 2025-05-20 09:50:15 +02:00
Your Name
c06741b11d Merge remote-tracking branch 'refs/remotes/origin/main' 2025-05-20 09:46:46 +02:00
Your Name
b1352261e7 update 2025-05-20 09:45:59 +02:00
Your Name
376ff454bf update 2025-05-20 09:33:14 +02:00
Your Name
932fdf83a2 update 2025-05-20 09:21:34 +02:00
Your Name
1ca3e2ada2 update 2025-05-20 09:14:25 +02:00
Your Name
fd1d32a62b udpate 2025-05-19 10:26:27 +02:00
Your Name
61b302fe35 update 2025-05-16 11:08:23 +02:00
Your Name
2aaa229e82 update 2025-05-14 17:37:53 +02:00
Your Name
fd28069b0c update 2025-05-14 17:24:01 +02:00
Your Name
b17c996f2f update 2025-05-13 17:14:29 +02:00
Your Name
8273307cab update 2025-05-07 18:48:47 +02:00
Your Name
a73eb30d32 update 2025-05-07 18:25:30 +02:00
Your Name
ba889feee9 update 2025-05-06 17:30:14 +02:00
Your Name
12c7a0b6af update 2025-05-01 11:01:29 +02:00
Your Name
08c5ed8841 update 2025-04-16 09:53:54 +02:00
Your Name
7f5eb7608c update 2025-04-16 09:46:12 +02:00
Your Name
44f44c3361 update 2025-04-07 15:57:58 +02:00
Your Name
a8350332ac update 2025-04-07 11:47:21 +02:00
Your Name
6c6eed1ad6 update 2025-04-03 10:49:55 +02:00
Your Name
ee71c28d33 update 2025-04-03 09:30:54 +02:00
Your Name
6d3220665e update 2025-04-03 09:07:20 +02:00
Your Name
98e5a239f5 update 2025-04-02 16:10:27 +02:00
Your Name
17f4ce46dd update 2025-04-02 15:09:10 +02:00
Your Name
338b8a049f update 2025-04-02 14:43:18 +02:00
Your Name
1e9e80ae55 update 2025-04-01 17:35:35 +02:00
Your Name
9d280c6e37 update 2025-04-01 11:57:39 +02:00
Your Name
d4c1178b3d update 2025-03-28 14:29:51 +01:00
Your Name
f7f6fccd60 update 2025-03-27 17:13:40 +01:00
Your Name
afceb34c1b update 2025-03-27 16:43:55 +01:00
Your Name
7a958d5c8e update 2025-03-27 16:40:24 +01:00
Your Name
8fd76001f2 update 2025-03-27 16:02:56 +01:00
Your Name
e320a3bc2b update 2025-03-27 16:02:05 +01:00
Your Name
8a4e184699 update 2025-03-27 15:58:22 +01:00
Your Name
e61b0a76da update 2025-03-27 15:50:59 +01:00
Your Name
970a36598c update 2025-03-26 10:30:24 +01:00
Your Name
e75caff929 update 2025-03-26 08:29:24 +01:00
Your Name
e82d75a4d6 update 2025-03-26 08:27:28 +01:00
Your Name
dc27e5f139 update 2025-03-26 08:25:42 +01:00
Your Name
4bc05091be update 2025-03-25 21:18:12 +01:00
Your Name
29f9ec445a Merge remote-tracking branch 'refs/remotes/origin/main' 2025-03-25 20:23:01 +01:00
Your Name
7b398d0d6d update 2025-03-25 20:22:42 +01:00
PaulVua
76336d0073 update 2025-03-25 16:27:01 +01:00
PaulVua
46a8e21e64 update 2025-03-25 16:20:19 +01:00
Your Name
2129d45ef6 update 2025-03-25 14:55:50 +01:00
Your Name
6312cd8d72 update 2025-03-24 18:04:19 +01:00
Your Name
7c17ec82f5 update 2025-03-24 17:57:47 +01:00
Your Name
b7a6f4c907 add sqlite config management 2025-03-24 15:19:27 +01:00
Your Name
6b3329b9b8 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-03-24 10:23:37 +01:00
PaulVua
e9b1e0e88e Merge remote-tracking branch 'refs/remotes/origin/main' 2025-03-24 10:21:25 +01:00
Your Name
2db732ebb3 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-03-20 17:46:09 +01:00
Your Name
d5302f78ba udpate 2025-03-20 17:45:47 +01:00
PaulVua
bca975b0c5 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-03-18 18:02:14 +01:00
PaulVua
dfba956685 update 2025-03-18 18:01:33 +01:00
52 changed files with 4198 additions and 1309 deletions

View File

@@ -12,9 +12,9 @@ GPIO 20 -> SARA PWR ON
option 1: option 1:
CLI tool like pinctrl CLI tool like pinctrl
pinctrl set 17 op pinctrl set 16 op
pinctrl set 17 dh pinctrl set 16 dh
pinctrl set 17 dl pinctrl set 16 dl
option 2: option 2:
python library RPI.GPIO python library RPI.GPIO

View File

@@ -52,9 +52,7 @@ def load_config(config_file):
return {} return {}
# Load the configuration data # Load the configuration data
config_file = '/var/www/nebuleair_pro_4g/config.json' npm_solo_port = "/dev/ttyAMA5" #port du NPM solo
config = load_config(config_file)
npm_solo_port = config.get('NPM_solo_port', '') #port du NPM solo
#GET RTC TIME from SQlite #GET RTC TIME from SQlite
cursor.execute("SELECT * FROM timestamp_table LIMIT 1") cursor.execute("SELECT * FROM timestamp_table LIMIT 1")

View File

@@ -28,7 +28,7 @@ Line by line installation.
``` ```
sudo apt update sudo apt update
sudo apt install git gh apache2 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus -y sudo apt install git gh apache2 sqlite3 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus -y
sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil ntplib pytz gpiozero adafruit-circuitpython-ads1x15 numpy --break-system-packages sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil ntplib pytz gpiozero adafruit-circuitpython-ads1x15 numpy --break-system-packages
sudo mkdir -p /var/www/.ssh sudo mkdir -p /var/www/.ssh
sudo ssh-keygen -t rsa -b 4096 -f /var/www/.ssh/id_rsa -N "" sudo ssh-keygen -t rsa -b 4096 -f /var/www/.ssh/id_rsa -N ""
@@ -36,9 +36,11 @@ sudo ssh-copy-id -i /var/www/.ssh/id_rsa.pub -p 50221 airlab_server1@aircarto.fr
sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git /var/www/nebuleair_pro_4g sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git /var/www/nebuleair_pro_4g
sudo mkdir /var/www/nebuleair_pro_4g/logs sudo mkdir /var/www/nebuleair_pro_4g/logs
sudo touch /var/www/nebuleair_pro_4g/logs/app.log /var/www/nebuleair_pro_4g/logs/loop.log /var/www/nebuleair_pro_4g/wifi_list.csv sudo touch /var/www/nebuleair_pro_4g/logs/app.log /var/www/nebuleair_pro_4g/logs/loop.log /var/www/nebuleair_pro_4g/wifi_list.csv
sudo cp /var/www/nebuleair_pro_4g/config.json.dist /var/www/nebuleair_pro_4g/config.json /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
sudo chmod -R 777 /var/www/nebuleair_pro_4g/ sudo chmod -R 777 /var/www/nebuleair_pro_4g/
git config --global core.fileMode false git config --global core.fileMode false
git -C /var/www/nebuleair_pro_4g config core.fileMode false
git config --global --add safe.directory /var/www/nebuleair_pro_4g git config --global --add safe.directory /var/www/nebuleair_pro_4g
sudo crontab /var/www/nebuleair_pro_4g/cron_jobs sudo crontab /var/www/nebuleair_pro_4g/cron_jobs
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
@@ -57,6 +59,8 @@ ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot
www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull www-data ALL=(ALL) NOPASSWD: /usr/bin/git pull
www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh www-data ALL=(ALL) NOPASSWD: /usr/bin/ssh
www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 * www-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *
www-data ALL=(ALL) NOPASSWD: /bin/systemctl *
www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*
``` ```
## Serial ## Serial

View File

@@ -25,23 +25,8 @@ url = parameter[1] # ex: data.mobileair.fr
profile_id = 3 profile_id = 3
#get baudrate baudrate = 115200
def load_config(config_file): send_uSpot = False
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
send_uSpot = config.get('send_uSpot', False)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_line=None): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_line=None):
response = bytearray() response = bytearray()

View File

@@ -26,23 +26,8 @@ url = parameter[1] # ex: data.mobileair.fr
profile_id = 3 profile_id = 3
#get baudrate baudrate = 115200
def load_config(config_file): send_uSpot = False
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
send_uSpot = config.get('send_uSpot', False)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
response = bytearray() response = bytearray()

View File

@@ -28,23 +28,8 @@ url = parameter[1] # ex: data.mobileair.fr
endpoint = parameter[2] endpoint = parameter[2]
profile_id = 2 profile_id = 2
#get baudrate baudrate = 115200
def load_config(config_file): send_uSpot = False
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
send_uSpot = config.get('send_uSpot', False)
def color_text(text, color): def color_text(text, color):
colors = { colors = {

View File

@@ -31,23 +31,8 @@ endpoint = parameter[2]
profile_id = 3 profile_id = 3
#get baudrate baudrate = 115200
def load_config(config_file): send_uSpot = False
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
send_uSpot = config.get('send_uSpot', False)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
response = bytearray() response = bytearray()

View File

@@ -21,23 +21,8 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
url = parameter[1] # ex: data.mobileair.fr url = parameter[1] # ex: data.mobileair.fr
#get baudrate baudrate = 115200
def load_config(config_file): send_uSpot = False
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
send_uSpot = config.get('send_uSpot', False)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
response = bytearray() response = bytearray()

View File

@@ -23,24 +23,8 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2 port='/dev/'+parameter[0] # ex: ttyAMA2
url = parameter[1] # ex: data.mobileair.fr url = parameter[1] # ex: data.mobileair.fr
baudrate = 115200
#get baudrate send_uSpot = False
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
send_uSpot = config.get('send_uSpot', False)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
response = bytearray() response = bytearray()

View File

@@ -14,19 +14,7 @@ parameter = sys.argv[1:] # Exclude the script name
port = '/dev/' + parameter[0] # e.g., ttyAMA2 port = '/dev/' + parameter[0] # e.g., ttyAMA2
timeout = float(parameter[1]) # e.g., 2 seconds timeout = float(parameter[1]) # e.g., 2 seconds
def load_config(config_file): baudrate = 115200
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
config = load_config(config_file)
baudrate = config.get('SaraR4_baudrate', 115200)
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2): def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
response = bytearray() response = bytearray()

View File

@@ -13,54 +13,105 @@ Script that starts at the boot of the RPI (with cron)
''' '''
import serial import serial
import RPi.GPIO as GPIO
import time import time
import sys import sys
import json import json
import re import re
import sqlite3
#get data from config #GPIO
def load_config(config_file): 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
# database connection
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
#get config data from SQLite table
def load_config_sqlite():
"""
Load configuration data from SQLite config table
Returns:
dict: Configuration data with proper type conversion
"""
try: try:
with open(config_file, 'r') as file:
config_data = json.load(file) # Query the config table
cursor.execute("SELECT key, value, type FROM config_table")
rows = cursor.fetchall()
# Create config dictionary
config_data = {}
for key, value, type_name in rows:
# Convert value based on its type
if type_name == 'bool':
config_data[key] = value == '1' or value == 'true'
elif type_name == 'int':
config_data[key] = int(value)
elif type_name == 'float':
config_data[key] = float(value)
else:
config_data[key] = value
return config_data return config_data
except Exception as e: except Exception as e:
print(f"Error loading config file: {e}") print(f"Error loading config from SQLite: {e}")
return {} return {}
#Fonction pour mettre à jour le JSON de configuration def update_sqlite_config(key, value):
def update_json_key(file_path, key, value):
""" """
Updates a specific key in a JSON file with a new value. Updates a specific key in the SQLite config_table with a new value.
:param file_path: Path to the JSON file. :param key: The key to update in the config_table.
:param key: The key to update in the JSON file.
:param value: The new value to assign to the key. :param value: The new value to assign to the key.
""" """
try: try:
# Load the existing data
with open(file_path, "r") as file:
data = json.load(file)
# Check if the key exists in the JSON file # Check if the key exists and get its type
if key in data: cursor.execute("SELECT type FROM config_table WHERE key = ?", (key,))
data[key] = value # Update the key with the new value result = cursor.fetchone()
else:
print(f"Key '{key}' not found in the JSON file.") if result is None:
print(f"Key '{key}' not found in the config_table.")
conn.close()
return return
# Write the updated data back to the file # Get the type of the value from the database
with open(file_path, "w") as file: value_type = result[0]
json.dump(data, file, indent=2) # Use indent for pretty printing
print(f"💾 updating '{key}' to '{value}'.") # Convert the value to the appropriate string representation based on its type
if value_type == 'bool':
# Convert Python boolean or string 'true'/'false' to '1'/'0'
if isinstance(value, bool):
str_value = '1' if value else '0'
else:
str_value = '1' if str(value).lower() in ('true', '1', 'yes', 'y') else '0'
elif value_type == 'int':
str_value = str(int(value))
elif value_type == 'float':
str_value = str(float(value))
else:
str_value = str(value)
# Update the value in the database
cursor.execute("UPDATE config_table SET value = ? WHERE key = ?", (str_value, key))
# Commit the changes and close the connection
conn.commit()
print(f"💾 Updated '{key}' to '{value}' in database.")
except Exception as e: except Exception as e:
print(f"Error updating the JSON file: {e}") print(f"Error updating the SQLite database: {e}")
# Define the config file path #Load config
config_file = '/var/www/nebuleair_pro_4g/config.json' config = load_config_sqlite()
# Load the configuration data #config
config = load_config(config_file)
baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4 baudrate = config.get('SaraR4_baudrate', 115200) #baudrate du sara R4
device_id = config.get('deviceID', '').upper() #device ID en maj device_id = config.get('deviceID', '').upper() #device ID en maj
@@ -122,6 +173,10 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
try: try:
print('<h3>Start reboot python script</h3>') print('<h3>Start reboot python script</h3>')
#First we need to power on the module (if connected to mosfet via gpio16)
GPIO.output(SARA_power_GPIO, GPIO.HIGH)
time.sleep(5)
#check modem status #check modem status
#Attention: #Attention:
# SARA R4 response: Manufacturer: u-blox Model: SARA-R410M-02B # SARA R4 response: Manufacturer: u-blox Model: SARA-R410M-02B
@@ -151,7 +206,7 @@ try:
print("⚠️ Could not identify modem model") print("⚠️ Could not identify modem model")
print(f"🔍 Model: {model}") print(f"🔍 Model: {model}")
update_json_key(config_file, "modem_version", model) update_sqlite_config("modem_version", model)
time.sleep(1) time.sleep(1)
''' '''
@@ -332,9 +387,9 @@ try:
else: else:
print("❌ Failed to extract coordinates.") print("❌ Failed to extract coordinates.")
#update config.json #update sqlite table
update_json_key(config_file, "latitude_raw", float(latitude)) update_sqlite_config("latitude_raw", float(latitude))
update_json_key(config_file, "longitude_raw", float(longitude)) update_sqlite_config("longitude_raw", float(longitude))
time.sleep(1) time.sleep(1)

View File

@@ -32,23 +32,8 @@ 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
#get baudrate
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables # Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200) baudrate = 115200
try: try:

View File

@@ -22,23 +22,7 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2 port='/dev/'+parameter[0] # ex: ttyAMA2
url = parameter[1] # ex: data.mobileair.fr url = parameter[1] # ex: data.mobileair.fr
baudrate = 115200
#get baudrate
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -26,22 +26,54 @@ networkID = parameter[1] # ex: 20801
timeout = float(parameter[2]) # ex:2 timeout = float(parameter[2]) # ex:2
#get baudrate
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json' def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
# Load the configuration data '''
config = load_config(config_file) Fonction très importante !!!
# Access the shared variables Reads the complete response from a serial connection and waits for specific lines.
baudrate = config.get('SaraR4_baudrate', 115200) '''
if wait_for_lines is None:
wait_for_lines = [] # Default to an empty list if not provided
response = bytearray()
serial_connection.timeout = timeout
end_time = time.time() + end_of_response_timeout
start_time = time.time()
while True:
elapsed_time = time.time() - start_time # Time since function start
if serial_connection.in_waiting > 0:
data = serial_connection.read(serial_connection.in_waiting)
response.extend(data)
end_time = time.time() + end_of_response_timeout # Reset timeout on new data
# Decode and check for any target line
decoded_response = response.decode('utf-8', errors='replace')
for target_line in wait_for_lines:
if target_line in decoded_response:
if debug:
print(f"[DEBUG] 🔎 Found target line: {target_line} (in {elapsed_time:.2f}s)")
return decoded_response # Return response immediately if a target line is found
elif time.time() > end_time:
if debug:
print(f"[DEBUG] Timeout reached. No more data received.")
break
time.sleep(0.1) # Short sleep to prevent busy waiting
# Final response and debug output
total_elapsed_time = time.time() - start_time
if debug:
print(f"[DEBUG] ⏱️ elapsed time: {total_elapsed_time:.2f}s. ⏱️")
# Check if the elapsed time exceeded 10 seconds
if total_elapsed_time > 10 and debug:
print(f"[ALERT] 🚨 The operation took too long 🚨")
print(f'<span style="color: red;font-weight: bold;">[ALERT] ⚠️{total_elapsed_time:.2f}s⚠</span>')
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
baudrate = 115200
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0
@@ -57,17 +89,11 @@ ser.write((command + '\r').encode('utf-8'))
try: try:
# Read lines until a timeout occurs response = read_complete_response(ser, wait_for_lines=["OK", "ERROR"],timeout=5, end_of_response_timeout=120, debug=True)
response_lines = []
while True:
line = ser.readline().decode('utf-8').strip()
if not line:
break # Break the loop if an empty line is encountered
response_lines.append(line)
# Print the response print('<p class="text-danger-emphasis">')
for line in response_lines: print(response)
print(line) print("</p>", end="")
except serial.SerialException as e: except serial.SerialException as e:
print(f"Error: {e}") print(f"Error: {e}")

View File

@@ -11,22 +11,7 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2 port='/dev/'+parameter[0] # ex: ttyAMA2
message = parameter[1] # ex: Hello message = parameter[1] # ex: Hello
#get baudrate baudrate = 115200
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -18,24 +18,7 @@ import sys
import json import json
baudrate = 115200
#get baudrate
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port='/dev/ttyAMA2', port='/dev/ttyAMA2',

View File

@@ -17,23 +17,7 @@ import json
# SARA R4 UHTTPC profile IDs # SARA R4 UHTTPC profile IDs
aircarto_profile_id = 0 aircarto_profile_id = 0
baudrate = 115200
#get baudrate
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser_sara = serial.Serial( ser_sara = serial.Serial(
port='/dev/ttyAMA2', port='/dev/ttyAMA2',

View File

@@ -11,22 +11,7 @@ parameter = sys.argv[1:] # Exclude the script name
port='/dev/'+parameter[0] # ex: ttyAMA2 port='/dev/'+parameter[0] # ex: ttyAMA2
message = parameter[1] # ex: Hello message = parameter[1] # ex: Hello
#get baudrate baudrate = 115200
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -12,22 +12,7 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
endpoint = parameter[1] # ex: /pro_4G/notif_message.php endpoint = parameter[1] # ex: /pro_4G/notif_message.php
profile_id = parameter[2] profile_id = parameter[2]
#get baudrate baudrate = 115200
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -21,23 +21,7 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
apn_address = parameter[1] # ex: data.mono apn_address = parameter[1] # ex: data.mono
timeout = float(parameter[2]) # ex:2 timeout = float(parameter[2]) # ex:2
baudrate = 115200
#get baudrate
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -27,22 +27,7 @@ url = parameter[1] # ex: data.mobileair.fr
profile_id = parameter[2] #ex: 0 profile_id = parameter[2] #ex: 0
#get baudrate baudrate = 115200
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -40,22 +40,7 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
return response.decode('utf-8', errors='replace') return response.decode('utf-8', errors='replace')
#get baudrate baudrate = 115200
def load_config(config_file):
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser_sara = serial.Serial( ser_sara = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -12,21 +12,7 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
message = parameter[1] # ex: Hello message = parameter[1] # ex: Hello
#get baudrate #get baudrate
def load_config(config_file): baudrate = 115200
try:
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Access the shared variables
baudrate = config.get('SaraR4_baudrate', 115200)
ser = serial.Serial( ser = serial.Serial(
port=port, #USB0 or ttyS0 port=port, #USB0 or ttyS0

View File

@@ -2,9 +2,10 @@
# Script to check if wifi is connected and start hotspot if not # Script to check if wifi is connected and start hotspot if not
# will also retreive unique RPi ID and store it to deviceID.txt # will also retreive unique RPi ID and store it to deviceID.txt
# script that starts at boot:
# @reboot /var/www/nebuleair_pro_4g/boot_hotspot.sh >> /var/www/nebuleair_pro_4g/logs/app.log 2>&1
OUTPUT_FILE="/var/www/nebuleair_pro_4g/wifi_list.csv" OUTPUT_FILE="/var/www/nebuleair_pro_4g/wifi_list.csv"
JSON_FILE="/var/www/nebuleair_pro_4g/config.json"
echo "-------------------" echo "-------------------"
@@ -27,15 +28,19 @@ for i in {1..5}; do
sleep 1 sleep 1
done done
echo "getting SARA R4 serial number" echo "getting RPI serial number"
# Get the last 8 characters of the serial number and write to text file # Get the last 8 characters of the serial number and write to text file
serial_number=$(cat /proc/cpuinfo | grep Serial | awk '{print substr($3, length($3) - 7)}') serial_number=$(cat /proc/cpuinfo | grep Serial | awk '{print substr($3, length($3) - 7)}')
# Use jq to update the "deviceID" in the JSON file
jq --arg serial_number "$serial_number" '.deviceID = $serial_number' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE" # update Sqlite database
echo "Updating SQLite database with device ID: $serial_number"
sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='$serial_number' WHERE key='deviceID';"
echo "id: $serial_number" echo "id: $serial_number"
#get the SSH port for tunneling
SSH_TUNNEL_PORT=$(jq -r '.sshTunnel_port' "$JSON_FILE") # Get SSH tunnel port from SQLite config_table
SSH_TUNNEL_PORT=$(sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "SELECT value FROM config_table WHERE key='sshTunnel_port'")
#need to wait for the network manager to be ready #need to wait for the network manager to be ready
sleep 20 sleep 20
@@ -53,19 +58,16 @@ if [ "$STATE" == "30 (disconnected)" ]; then
echo "Starting hotspot..." echo "Starting hotspot..."
sudo nmcli device wifi hotspot ifname wlan0 ssid nebuleair_pro password nebuleaircfg sudo nmcli device wifi hotspot ifname wlan0 ssid nebuleair_pro password nebuleaircfg
# Update JSON to reflect hotspot mode # Update SQLite to reflect hotspot mode
jq --arg status "hotspot" '.WIFI_status = $status' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE" sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='hotspot' WHERE key='WIFI_status'"
else else
echo "🛜Success: wlan0 is connected!🛜" echo "🛜Success: wlan0 is connected!🛜"
CONN_SSID=$(nmcli -g GENERAL.CONNECTION device show wlan0) CONN_SSID=$(nmcli -g GENERAL.CONNECTION device show wlan0)
echo "Connection: $CONN_SSID" echo "Connection: $CONN_SSID"
#update config JSON file # Update SQLite to reflect hotspot mode
jq --arg status "connected" '.WIFI_status = $status' "$JSON_FILE" > temp.json && mv temp.json "$JSON_FILE" sqlite3 /var/www/nebuleair_pro_4g/sqlite/sensors.db "UPDATE config_table SET value='connected' WHERE key='WIFI_status'"
sudo chmod 777 "$JSON_FILE"
# Lancer le tunnel SSH # Lancer le tunnel SSH
#echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..." #echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..."

View File

@@ -4,7 +4,10 @@
@reboot sleep 30 && /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/reboot/start.py >> /var/www/nebuleair_pro_4g/logs/app.log 2>&1 @reboot sleep 30 && /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/reboot/start.py >> /var/www/nebuleair_pro_4g/logs/app.log 2>&1
0 0 * * * > /var/www/nebuleair_pro_4g/logs/master.log #0 0 * * * > /var/www/nebuleair_pro_4g/logs/master.log
0 0 * * * > /var/www/nebuleair_pro_4g/logs/master_errors.log #0 0 * * * > /var/www/nebuleair_pro_4g/logs/master_errors.log
0 0 * * * > /var/www/nebuleair_pro_4g/logs/app.log #0 0 * * * > /var/www/nebuleair_pro_4g/logs/app.log
0 0 * * * find /var/www/nebuleair_pro_4g/logs -name "*.log" -type f -exec truncate -s 0 {} \;

View File

@@ -28,31 +28,14 @@ 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'
# Function to load config data # Fetch connected ENVEA sondes from SQLite config table
def load_config(config_file): cursor.execute("SELECT port, name, coefficient FROM envea_sondes_table WHERE connected = 1")
try: connected_envea_sondes = cursor.fetchall() # List of tuples (port, name, coefficient)
with open(config_file, 'r') as file:
config_data = json.load(file)
return config_data
except Exception as e:
print(f"Error loading config file: {e}")
return {}
# Define the config file path
config_file = '/var/www/nebuleair_pro_4g/config.json'
# Load the configuration data
config = load_config(config_file)
# Initialize sensors and serial connections
envea_sondes = config.get('envea_sondes', [])
connected_envea_sondes = [sonde for sonde in envea_sondes if sonde.get('connected', False)]
serial_connections = {} serial_connections = {}
if connected_envea_sondes: if connected_envea_sondes:
for device in connected_envea_sondes: for port, name, coefficient in connected_envea_sondes:
port = device.get('port', 'Unknown')
name = device.get('name', 'Unknown')
try: try:
serial_connections[name] = serial.Serial( serial_connections[name] = serial.Serial(
port=f'/dev/{port}', port=f'/dev/{port}',
@@ -74,9 +57,7 @@ data_nh3 = 0
try: try:
if connected_envea_sondes: if connected_envea_sondes:
for device in connected_envea_sondes: for port, name, coefficient in connected_envea_sondes:
name = device.get('name', 'Unknown')
coefficient = device.get('coefficient', 1)
if name in serial_connections: if name in serial_connections:
serial_connection = serial_connections[name] serial_connection = serial_connections[name]
try: try:

File diff suppressed because it is too large Load Diff

View File

@@ -71,6 +71,8 @@
<button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button> <button class="btn btn-primary" onclick="get_data_sqlite('data_BME280',getSelectedLimit(),false)">Mesures Temp/Hum</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button> <button class="btn btn-primary" onclick="get_data_sqlite('data_NPM_5channels',getSelectedLimit(),false)">Mesures PM (5 canaux)</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button> <button class="btn btn-primary" onclick="get_data_sqlite('data_envea',getSelectedLimit(),false)">Sonde Cairsens</button>
<button class="btn btn-primary" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)">Sonde Vent</button>
<button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button> <button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button>
</div> </div>
@@ -147,23 +149,37 @@
window.onload = function() { window.onload = function() {
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON
.then(data => {
console.log("Getting config file (onload)");
//get device ID
const deviceID = data.deviceID.trim().toUpperCase();
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
//get device Name
const deviceName = data.deviceName;
//NEW way to get data from SQLITE
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//get device Name (for the side bar)
const deviceName = response.deviceName;
const elements = document.querySelectorAll('.sideBar_sensorName'); const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => { elements.forEach((element) => {
element.innerText = deviceName; element.innerText = deviceName;
}); });
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
}); //end ajax
//get local RTC //get local RTC
$.ajax({ $.ajax({
@@ -178,11 +194,10 @@
error: function(xhr, status, error) { error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error); console.error('AJAX request failed:', status, error);
} }
}); }); //end AJAX
})
.catch(error => console.error('Error loading config.json:', error)); }
}
@@ -199,7 +214,6 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
console.log(url); console.log(url);
$.ajax({ $.ajax({
url: url, url: url,
dataType: 'text', // Specify that you expect a JSON response dataType: 'text', // Specify that you expect a JSON response
@@ -260,6 +274,12 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
tableHTML += ` tableHTML += `
<th>Timestamp</th> <th>Timestamp</th>
`; `;
}else if (table === "data_WIND") {
tableHTML += `
<th>Timestamp</th>
<th>speed (km/h)</th>
<th>Direction (V)</th>
`;
} }
tableHTML += `</tr></thead><tbody>`; tableHTML += `</tr></thead><tbody>`;
@@ -310,6 +330,12 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
tableHTML += ` tableHTML += `
<td>${columns[1]}</td> <td>${columns[1]}</td>
`; `;
}else if (table === "data_WIND") {
tableHTML += `
<td>${columns[0]}</td>
<td>${columns[1]}</td>
<td>${columns[2]}</td>
`;
} }
tableHTML += "</tr>"; tableHTML += "</tr>";

View File

@@ -135,6 +135,35 @@
window.onload = function() { window.onload = function() {
//NEW way to get data from SQLITE
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//get device Name (for the side bar)
const deviceName = response.deviceName;
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = deviceName;
});
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
}); //end ajax
/* OLD way of getting config data
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json' fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON .then(response => response.json()) // Parse response as JSON
.then(data => { .then(data => {
@@ -152,6 +181,11 @@ window.onload = function() {
element.innerText = deviceName; element.innerText = deviceName;
}); });
//end fetch config
})
.catch(error => console.error('Error loading config.json:', error));
//end windows on load
*/
//get local RTC //get local RTC
$.ajax({ $.ajax({
url: 'launcher.php?type=RTC_time', url: 'launcher.php?type=RTC_time',
@@ -421,10 +455,6 @@ window.onload = function() {
//end fetch config
})
.catch(error => console.error('Error loading config.json:', error));
//end windows on load
} }
</script> </script>

View File

@@ -1,13 +1,16 @@
<?php <?php
//Prevents caching → Adds headers to ensure fresh response. //Prevents caching → Adds headers to ensure fresh response.
// to test this page http://192.168.1.127/html/launcher.php?type=get_config_scripts_sqlite
header("Content-Type: application/json"); header("Content-Type: application/json");
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache"); header("Pragma: no-cache");
$database_path = "/var/www/nebuleair_pro_4g/sqlite/sensors.db";
$type=$_GET['type']; $type=$_GET['type'];
if ($type == "get_npm_sqlite_data") { if ($type == "get_npm_sqlite_data") {
$database_path = "/var/www/nebuleair_pro_4g/sqlite/sensors.db";
//echo "Getting data from sqlite database"; //echo "Getting data from sqlite database";
try { try {
$db = new PDO("sqlite:$database_path"); $db = new PDO("sqlite:$database_path");
@@ -25,8 +28,281 @@ if ($type == "get_npm_sqlite_data") {
} }
} }
/*
*/
//GETING data from config_table (SQLite DB)
if ($type == "get_config_sqlite") {
try {
$db = new PDO("sqlite:$database_path");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Get all main configuration entries
$config_query = $db->query("SELECT key, value, type FROM config_table");
$config_data = $config_query->fetchAll(PDO::FETCH_ASSOC);
// Convert data types according to their 'type' field
$result = [];
foreach ($config_data as $item) {
$key = $item['key'];
$value = $item['value'];
$type = $item['type'];
// Convert value based on its type
switch ($type) {
case 'bool':
$parsed_value = ($value == '1' || $value == 'true') ? true : false;
break;
case 'int':
$parsed_value = intval($value);
break;
case 'float':
$parsed_value = floatval($value);
break;
default:
$parsed_value = $value;
}
$result[$key] = $parsed_value;
}
// Return JSON response
header('Content-Type: application/json');
echo json_encode($result, JSON_PRETTY_PRINT);
} catch (PDOException $e) {
// Return error as JSON
header('Content-Type: application/json');
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}
}
/*
*/
//GETING data from config_scrips_table (SQLite DB)
if ($type == "get_config_scripts_sqlite") {
try {
$db = new PDO("sqlite:$database_path");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Get all main configuration entries
$config_query = $db->query("SELECT * FROM config_scripts_table");
$config_data = $config_query->fetchAll(PDO::FETCH_ASSOC);
// Convert data types according to their 'type' field
$result = [];
foreach ($config_data as $item) {
$script_path = $item['script_path'];
$enabled = $item['enabled'];
// Convert the enabled field to a proper boolean
$result[$script_path] = ($enabled == 1);
}
// Return JSON response
header('Content-Type: application/json');
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} catch (PDOException $e) {
// Return error as JSON
header('Content-Type: application/json');
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}
}
/*
*/
//GETING data from envea_sondes_table (SQLite DB)
if ($type == "get_envea_sondes_table_sqlite") {
try {
$db = new PDO("sqlite:$database_path");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Get all entries from envea_sondes_table
$query = $db->query("SELECT id, connected, port, name, coefficient FROM envea_sondes_table");
$data = $query->fetchAll(PDO::FETCH_ASSOC);
// Convert data types appropriately
$result = [];
foreach ($data as $item) {
// Create object for each sonde with proper data types
$sonde = [
'id' => (int)$item['id'],
'connected' => $item['connected'] == 1, // Convert to boolean
'port' => $item['port'],
'name' => $item['name'],
'coefficient' => (float)$item['coefficient'] // Convert to float
];
// Add to results array
$result[] = $sonde;
}
// Return JSON response
header('Content-Type: application/json');
echo json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
} catch (PDOException $e) {
// Return error as JSON
header('Content-Type: application/json');
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}
}
//UPDATING the config_table from SQLite DB
if ($type == "update_config_sqlite") {
$param = $_GET['param'] ?? null;
$value = $_GET['value'] ?? null;
if ($param === null || $value === null) {
echo json_encode(["error" => "Missing parameter or value"]);
exit;
}
try {
$db = new PDO("sqlite:$database_path");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// First, check if parameter exists and get its type
$checkStmt = $db->prepare("SELECT type FROM config_table WHERE key = :param");
$checkStmt->bindParam(':param', $param);
$checkStmt->execute();
$result = $checkStmt->fetch(PDO::FETCH_ASSOC);
if ($result) {
// Parameter exists, determine type and update
$type = $result['type'];
// Convert value according to type if needed
$convertedValue = $value;
if ($type == "bool") {
// Convert various boolean representations to 0/1
$convertedValue = (filter_var($value, FILTER_VALIDATE_BOOLEAN)) ? "1" : "0";
} elseif ($type == "int") {
$convertedValue = (string)intval($value);
} elseif ($type == "float") {
$convertedValue = (string)floatval($value);
}
// Update the value
$updateStmt = $db->prepare("UPDATE config_table SET value = :value WHERE key = :param");
$updateStmt->bindParam(':value', $convertedValue);
$updateStmt->bindParam(':param', $param);
$updateStmt->execute();
echo json_encode([
"success" => true,
"message" => "Configuration updated successfully",
"param" => $param,
"value" => $convertedValue,
"type" => $type
]);
} else {
echo json_encode([
"error" => "Parameter not found in configuration",
"param" => $param
]);
}
} catch (PDOException $e) {
echo json_encode(["error" => $e->getMessage()]);
}
}
//UPDATING the envea_sondes_table table from SQLite DB
if ($type == "update_sonde") {
$id = $_GET['id'] ?? null;
$field = $_GET['field'] ?? null;
$value = $_GET['value'] ?? null;
// Validate parameters
if ($id === null || $field === null || $value === null) {
echo json_encode([
"success" => false,
"error" => "Missing required parameters (id, field, or value)"
]);
exit;
}
// Validate field name (whitelist approach for security)
$allowed_fields = ['connected', 'port', 'name', 'coefficient'];
if (!in_array($field, $allowed_fields)) {
echo json_encode([
"success" => false,
"error" => "Invalid field name: " . $field
]);
exit;
}
try {
// Connect to the database
$db = new PDO("sqlite:$database_path");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Check if the sonde exists
$checkStmt = $db->prepare("SELECT id FROM envea_sondes_table WHERE id = :id");
$checkStmt->bindParam(':id', $id, PDO::PARAM_INT);
$checkStmt->execute();
if (!$checkStmt->fetch()) {
echo json_encode([
"success" => false,
"error" => "Sonde with ID $id not found"
]);
exit;
}
// Process value based on field type
if ($field == 'connected') {
// Convert to integer (0 or 1)
$processedValue = filter_var($value, FILTER_VALIDATE_BOOLEAN) ? 1 : 0;
$paramType = PDO::PARAM_INT;
} else if ($field == 'coefficient') {
// Convert to float
$processedValue = floatval($value);
$paramType = PDO::PARAM_STR; // SQLite doesn't have PARAM_FLOAT
} else {
// For text fields (port, name)
$processedValue = $value;
$paramType = PDO::PARAM_STR;
}
// Update the sonde record
$updateStmt = $db->prepare("UPDATE envea_sondes_table SET $field = :value WHERE id = :id");
$updateStmt->bindParam(':value', $processedValue, $paramType);
$updateStmt->bindParam(':id', $id, PDO::PARAM_INT);
$updateStmt->execute();
// Return success response
echo json_encode([
"success" => true,
"message" => "Sonde $id updated successfully",
"field" => $field,
"value" => $processedValue
]);
} catch (PDOException $e) {
// Return error as JSON
echo json_encode([
"success" => false,
"error" => "Database error: " . $e->getMessage()
]);
}
}
//update the config (old JSON updating)
if ($type == "update_config") { if ($type == "update_config") {
echo "updating...."; echo "updating.... ";
$param=$_GET['param']; $param=$_GET['param'];
$value=$_GET['value']; $value=$_GET['value'];
$configFile = '../config.json'; $configFile = '../config.json';
@@ -73,6 +349,20 @@ if ($type == "git_pull") {
echo $output; echo $output;
} }
if ($type == "update_firmware") {
// Execute the comprehensive update script
$command = 'sudo /var/www/nebuleair_pro_4g/update_firmware.sh 2>&1';
$output = shell_exec($command);
// Return the output as JSON for better web display
header('Content-Type: application/json');
echo json_encode([
'success' => true,
'output' => $output,
'timestamp' => date('Y-m-d H:i:s')
]);
}
if ($type == "set_RTC_withNTP") { if ($type == "set_RTC_withNTP") {
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py'; $command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
$output = shell_exec($command); $output = shell_exec($command);
@@ -101,9 +391,50 @@ if ($type == "set_RTC_withBrowser") {
if ($type == "clear_loopLogs") { if ($type == "clear_loopLogs") {
$command = 'truncate -s 0 /var/www/nebuleair_pro_4g/logs/loop.log'; $response = array();
$output = shell_exec($command);
echo $output; try {
$logPath = '/var/www/nebuleair_pro_4g/logs/master.log';
// Check if file exists and is writable
if (!file_exists($logPath)) {
throw new Exception("Log file does not exist");
}
if (!is_writable($logPath)) {
throw new Exception("Log file is not writable");
}
// Execute the command
$command = 'truncate -s 0 ' . escapeshellarg($logPath);
$output = shell_exec($command . ' 2>&1');
// Check if there was any error output
if (!empty($output)) {
throw new Exception("Command error: " . $output);
}
// Success response
$response = array(
'status' => 'success',
'message' => 'Logs cleared successfully',
'timestamp' => date('Y-m-d H:i:s')
);
} catch (Exception $e) {
// Error response
$response = array(
'status' => 'error',
'message' => $e->getMessage(),
'timestamp' => date('Y-m-d H:i:s')
);
}
// Set content type to JSON
header('Content-Type: application/json');
// Return the JSON response
echo json_encode($response);
exit;
} }
if ($type == "database_size") { if ($type == "database_size") {
@@ -268,38 +599,58 @@ if ($type == "sara_connectNetwork") {
$port=$_GET['port']; $port=$_GET['port'];
$timeout=$_GET['timeout']; $timeout=$_GET['timeout'];
$networkID=$_GET['networkID']; $networkID=$_GET['networkID'];
$param="SARA_R4_neworkID";
//echo "updating SARA_R4_networkID in config file";
//OLD way to store data (JSON file)
echo "updating SARA_R4_networkID in config file";
// Convert `networkID` to an integer (or float if needed) // Convert `networkID` to an integer (or float if needed)
$networkID = is_numeric($networkID) ? (strpos($networkID, '.') !== false ? (float)$networkID : (int)$networkID) : 0; //$networkID = is_numeric($networkID) ? (strpos($networkID, '.') !== false ? (float)$networkID : (int)$networkID) : 0;
#save to config.json #save to config.json
$configFile = '/var/www/nebuleair_pro_4g/config.json'; //$configFile = '/var/www/nebuleair_pro_4g/config.json';
// Read the JSON file // Read the JSON file
$jsonData = file_get_contents($configFile); //$jsonData = file_get_contents($configFile);
// Decode JSON data into an associative array // Decode JSON data into an associative array
$config = json_decode($jsonData, true); //$config = json_decode($jsonData, true);
// Check if decoding was successful // Check if decoding was successful
if ($config === null) { //if ($config === null) {
die("Error: Could not decode JSON file."); // die("Error: Could not decode JSON file.");
} //}
// Update the value of SARA_R4_networkID // Update the value of SARA_R4_networkID
$config['SARA_R4_neworkID'] = $networkID; // Replace 42 with the desired value //$config['SARA_R4_neworkID'] = $networkID; // Replace 42 with the desired value
// Encode the array back to JSON with pretty printing // Encode the array back to JSON with pretty printing
$newJsonData = json_encode($config, JSON_PRETTY_PRINT); //$newJsonData = json_encode($config, JSON_PRETTY_PRINT);
// Check if encoding was successful // Check if encoding was successful
if ($newJsonData === false) { //if ($newJsonData === false) {
die("Error: Could not encode JSON data."); // die("Error: Could not encode JSON data.");
} //}
// Write the updated JSON back to the file // Write the updated JSON back to the file
if (file_put_contents($configFile, $newJsonData) === false) { //if (file_put_contents($configFile, $newJsonData) === false) {
die("Error: Could not write to JSON file."); // die("Error: Could not write to JSON file.");
} //}
//echo "SARA_R4_networkID updated successfully.";
//NEW way to store data -> use SQLITE
try {
$db = new PDO("sqlite:$database_path");
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$updateStmt = $db->prepare("UPDATE config_table SET value = :value WHERE key = :param");
$updateStmt->bindParam(':value', $networkID);
$updateStmt->bindParam(':param', $param);
$updateStmt->execute();
echo "SARA_R4_networkID updated successfully."; echo "SARA_R4_networkID updated successfully.";
} catch (PDOException $e) {
// Return error as JSON
header('Content-Type: application/json');
echo json_encode(['error' => 'Database error: ' . $e->getMessage()]);
}
echo "connecting to network... please wait..."; //echo "connecting to network... please wait...";
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout; $command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout;
$output = shell_exec($command); $output = shell_exec($command);
echo $output; echo $output;
@@ -484,3 +835,366 @@ if ($type == "wifi_scan_old") {
echo $json_data; echo $json_data;
} }
/*
_____ _ _
|_ _|__ _ __ _ __ ___ (_)_ __ __ _| |
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | |
| | __/ | | | | | | | | | | | (_| | |
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_|
*/
// Execute shell command with security restrictions
if ($type == "execute_command") {
// Verify that the request is using POST method
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
echo json_encode(['success' => false, 'message' => 'Invalid request method']);
exit;
}
// Get the command from POST data
$command = isset($_POST['command']) ? $_POST['command'] : '';
if (empty($command)) {
echo json_encode(['success' => false, 'message' => 'No command provided']);
exit;
}
// List of allowed commands (prefixes)
$allowedCommands = [
'ls', 'cat', 'cd', 'pwd', 'df', 'free', 'ifconfig', 'ip', 'ps', 'date', 'uptime',
'systemctl status', 'whoami', 'hostname', 'uname', 'grep', 'tail', 'head', 'find',
'less', 'more', 'du', 'echo', 'git'
];
// Check if command is allowed
$allowed = false;
foreach ($allowedCommands as $allowedCmd) {
if (strpos($command, $allowedCmd) === 0) {
$allowed = true;
break;
}
}
// Special case for systemctl restart and reboot
if (strpos($command, 'systemctl restart') === 0 || $command === 'reboot') {
// These commands don't return output through shell_exec since they change process state
// We'll just acknowledge them
if ($command === 'reboot') {
// Execute the command with exec to avoid waiting for output
exec('sudo reboot > /dev/null 2>&1 &');
echo json_encode([
'success' => true,
'output' => 'System is rebooting...'
]);
} else {
// For systemctl restart, execute it and acknowledge
$serviceName = str_replace('systemctl restart ', '', $command);
exec('sudo systemctl restart ' . escapeshellarg($serviceName) . ' > /dev/null 2>&1 &');
echo json_encode([
'success' => true,
'output' => 'Service ' . $serviceName . ' is restarting...'
]);
}
exit;
}
// Check for prohibited patterns
$prohibitedPatterns = [
'sudo rm', ';', '&&', '||', '|', '>', '>>', '&',
'wget', 'curl', 'nc', 'ssh', 'scp', 'ftp', 'telnet',
'iptables', 'passwd', 'chown', 'chmod', 'mkfs', ' dd ',
'mount', 'umount', 'kill', 'killall'
];
foreach ($prohibitedPatterns as $pattern) {
if (strpos($command, $pattern) !== false) {
echo json_encode([
'success' => false,
'message' => 'Command contains prohibited operation: ' . $pattern
]);
exit;
}
}
if (!$allowed) {
echo json_encode([
'success' => false,
'message' => 'Command not allowed for security reasons'
]);
exit;
}
// Execute the command with timeout protection
$descriptorspec = [
0 => ["pipe", "r"], // stdin
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"] // stderr
];
// Escape the command to prevent shell injection
$escapedCommand = escapeshellcmd($command);
// Add timeout of 5 seconds to prevent long-running commands
$process = proc_open("timeout 5 $escapedCommand", $descriptorspec, $pipes);
if (is_resource($process)) {
// Close stdin pipe
fclose($pipes[0]);
// Get output from stdout
$output = stream_get_contents($pipes[1]);
fclose($pipes[1]);
// Get any errors
$errors = stream_get_contents($pipes[2]);
fclose($pipes[2]);
// Close the process
$returnValue = proc_close($process);
// Check for errors
if ($returnValue !== 0) {
// If there was an error, but we have output, consider it a partial success
if (!empty($output)) {
echo json_encode([
'success' => true,
'output' => $output . "\n" . $errors . "\nCommand exited with code $returnValue"
]);
} else {
echo json_encode([
'success' => false,
'message' => empty($errors) ? "Command failed with exit code $returnValue" : $errors
]);
}
} else {
// Success
echo json_encode([
'success' => true,
'output' => $output
]);
}
} else {
echo json_encode([
'success' => false,
'message' => 'Failed to execute command'
]);
}
}
/*
____ _ ____ _ __ __ _
/ ___| _ _ ___| |_ ___ _ __ ___ | _ \ / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
\___ \| | | / __| __/ _ \ '_ ` _ \| | | | \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
___) | |_| \__ \ || __/ | | | | | |_| | ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
|____/ \__, |___/\__\___|_| |_| |_|____/ |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
|___/ |___/
*/
// Get systemd services status
if ($type == "get_systemd_services") {
try {
// List of NebuleAir services to monitor with descriptions and frequencies
$services = [
'nebuleair-npm-data.timer' => [
'description' => 'Collects particulate matter data from NextPM sensor',
'frequency' => 'Every 10 seconds'
],
'nebuleair-envea-data.timer' => [
'description' => 'Reads environmental data from Envea sensors',
'frequency' => 'Every 10 seconds'
],
'nebuleair-sara-data.timer' => [
'description' => 'Transmits collected data via 4G cellular modem',
'frequency' => 'Every 60 seconds'
],
'nebuleair-bme280-data.timer' => [
'description' => 'Monitors temperature and humidity (BME280)',
'frequency' => 'Every 2 minutes'
],
'nebuleair-mppt-data.timer' => [
'description' => 'Tracks solar panel and battery status',
'frequency' => 'Every 2 minutes'
],
'nebuleair-db-cleanup-data.timer' => [
'description' => 'Cleans up old data from database',
'frequency' => 'Daily'
]
];
$serviceStatus = [];
foreach ($services as $service => $serviceInfo) {
// Get service active status
$activeCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null";
$activeStatus = trim(shell_exec($activeCmd));
$isActive = ($activeStatus === 'active');
// Get service enabled status
$enabledCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null";
$enabledStatus = trim(shell_exec($enabledCmd));
$isEnabled = ($enabledStatus === 'enabled');
// Clean up service name for display
$displayName = str_replace(['.timer', 'nebuleair-', '-data'], '', $service);
$displayName = ucfirst(str_replace('-', ' ', $displayName));
$serviceStatus[] = [
'name' => $service,
'display_name' => $displayName,
'description' => $serviceInfo['description'],
'frequency' => $serviceInfo['frequency'],
'active' => $isActive,
'enabled' => $isEnabled
];
}
echo json_encode([
'success' => true,
'services' => $serviceStatus
], JSON_PRETTY_PRINT);
} catch (Exception $e) {
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
}
// Restart a systemd service
if ($type == "restart_systemd_service") {
$service = $_GET['service'] ?? null;
if (empty($service)) {
echo json_encode([
'success' => false,
'error' => 'No service specified'
]);
exit;
}
// Validate service name (security check)
$allowedServices = [
'nebuleair-npm-data.timer',
'nebuleair-envea-data.timer',
'nebuleair-sara-data.timer',
'nebuleair-bme280-data.timer',
'nebuleair-mppt-data.timer',
'nebuleair-db-cleanup-data.timer'
];
if (!in_array($service, $allowedServices)) {
echo json_encode([
'success' => false,
'error' => 'Invalid service name'
]);
exit;
}
try {
// Restart the service
$command = "sudo systemctl restart " . escapeshellarg($service) . " 2>&1";
$output = shell_exec($command);
// Check if restart was successful
$statusCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null";
$status = trim(shell_exec($statusCmd));
if ($status === 'active' || empty($output)) {
echo json_encode([
'success' => true,
'message' => "Service $service restarted successfully"
]);
} else {
echo json_encode([
'success' => false,
'error' => "Failed to restart service: $output"
]);
}
} catch (Exception $e) {
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
}
// Enable/disable a systemd service
if ($type == "toggle_systemd_service") {
$service = $_GET['service'] ?? null;
$enable = $_GET['enable'] ?? null;
if (empty($service) || $enable === null) {
echo json_encode([
'success' => false,
'error' => 'Missing service name or enable parameter'
]);
exit;
}
// Validate service name (security check)
$allowedServices = [
'nebuleair-npm-data.timer',
'nebuleair-envea-data.timer',
'nebuleair-sara-data.timer',
'nebuleair-bme280-data.timer',
'nebuleair-mppt-data.timer',
'nebuleair-db-cleanup-data.timer'
];
if (!in_array($service, $allowedServices)) {
echo json_encode([
'success' => false,
'error' => 'Invalid service name'
]);
exit;
}
try {
$enable = filter_var($enable, FILTER_VALIDATE_BOOLEAN);
$action = $enable ? 'enable' : 'disable';
// Enable/disable the service
$command = "sudo systemctl $action " . escapeshellarg($service) . " 2>&1";
$output = shell_exec($command);
// If disabling, also stop the service
if (!$enable) {
$stopCommand = "sudo systemctl stop " . escapeshellarg($service) . " 2>&1";
$stopOutput = shell_exec($stopCommand);
}
// If enabling, also start the service
if ($enable) {
$startCommand = "sudo systemctl start " . escapeshellarg($service) . " 2>&1";
$startOutput = shell_exec($startCommand);
}
// Check if the operation was successful
$statusCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null";
$status = trim(shell_exec($statusCmd));
$expectedStatus = $enable ? 'enabled' : 'disabled';
if ($status === $expectedStatus) {
echo json_encode([
'success' => true,
'message' => "Service $service " . ($enable ? 'enabled and started' : 'disabled and stopped') . " successfully"
]);
} else {
echo json_encode([
'success' => false,
'error' => "Failed to $action service: $output"
]);
}
} catch (Exception $e) {
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
}

View File

@@ -56,7 +56,10 @@
<div class="col-lg-6 col-12"> <div class="col-lg-6 col-12">
<div class="card" style="height: 80vh;"> <div class="card" style="height: 80vh;">
<div class="card-header"> <div class="card-header">
Master logs <button type="submit" class="btn btn-secondary btn-sm" onclick="clear_loopLogs()">Clear</button> Sara logs
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-master-log">Refresh</button>
<button type="submit" class="btn btn-secondary btn-sm" onclick="clear_loopLogs()">Clear</button>
<span id="script_running"></span> <span id="script_running"></span>
</div> </div>
<div class="card-body overflow-auto" id="card_loop_content"> <div class="card-body overflow-auto" id="card_loop_content">
@@ -69,6 +72,7 @@
<div class="card" style="height: 80vh;"> <div class="card" style="height: 80vh;">
<div class="card-header"> <div class="card-header">
Boot logs Boot logs
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-boot-log">Refresh</button>
</div> </div>
<div class="card-body overflow-auto" id="card_boot_content"> <div class="card-body overflow-auto" id="card_boot_content">
@@ -111,65 +115,17 @@
const boot_card_content = document.getElementById('card_boot_content'); const boot_card_content = document.getElementById('card_boot_content');
//Getting Master logs //Getting Master logs
console.log("Getting master logs"); console.log("Getting SARA logs");
displayLogFile('../logs/sara_service.log', loop_card_content, true, 1000);
fetch('../logs/master.log')
.then((response) => {
console.log("OK");
if (!response.ok) {
throw new Error('Failed to fetch the log file.');
}
return response.text();
})
.then((data) => {
const lines = data.split('\n');
// Format log content
const formattedLog = lines
.map((line) => line.trim()) // Remove extra whitespace
.filter((line) => line) // Remove empty lines
.join('<br>'); // Join formatted lines with line breaks
loop_card_content.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${formattedLog}</pre>`;
loop_card_content.scrollTop = loop_card_content.scrollHeight; // Scroll to the bottom
})
.catch((error) => {
console.error(error);
loop_card_content.textContent = 'Error loading log file.';
});
console.log("Getting app/boot logs"); console.log("Getting app/boot logs");
displayLogFile('../logs/app.log', boot_card_content, true, 1000);
//Getting App logs // Setup master log with refresh button
fetch('../logs/app.log') setupLogRefreshButton('refresh-master-log', '../logs/sara_service.log', 'card_loop_content', 3000);
.then((response) => {
console.log("OK");
if (!response.ok) {
throw new Error('Failed to fetch the log file.');
}
return response.text();
})
.then((data) => {
const lines = data.split('\n');
// Format log content
const formattedLog = lines
.map((line) => line.trim()) // Remove extra whitespace
.filter((line) => line) // Remove empty lines
.join('<br>'); // Join formatted lines with line breaks
boot_card_content.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${formattedLog}</pre>`;
boot_card_content.scrollTop = loop_card_content.scrollHeight; // Scroll to the bottom
})
.catch((error) => {
console.error(error);
boot_card_content.textContent = 'Error loading log file.';
});
// Setup boot log with refresh button
setupLogRefreshButton('refresh-boot-log', '../logs/app.log', 'card_boot_content', 300);
}); });
@@ -179,21 +135,33 @@ window.onload = function() {
getModem_busy_status(); getModem_busy_status();
setInterval(getModem_busy_status, 2000); setInterval(getModem_busy_status, 2000);
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json' //NEW way to get config (SQLite)
.then(response => response.json()) // Parse response as JSON $.ajax({
.then(data => { url: 'launcher.php?type=get_config_sqlite',
console.log("Getting config file (onload)"); dataType:'json',
//get device ID //dataType: 'json', // Specify that you expect a JSON response
const deviceID = data.deviceID.trim().toUpperCase(); method: 'GET', // Use GET or POST depending on your needs
// document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID; success: function(response) {
//get device Name console.log("Getting SQLite config table:");
const deviceName = data.deviceName; console.log(response);
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName'); const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => { elements.forEach((element) => {
element.innerText = deviceName; element.innerText = response.deviceName;
}); });
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//get local RTC //get local RTC
$.ajax({ $.ajax({
@@ -210,10 +178,78 @@ window.onload = function() {
} }
}); });
}//end onload
function displayLogFile(logFilePath, containerElement, scrollToBottom = true, maxLines = 0) {
// Show loading indicator
containerElement.innerHTML = '<div class="text-center"><i>Loading log file...</i></div>';
return fetch(logFilePath)
.then((response) => {
if (!response.ok) {
throw new Error(`Failed to fetch the log file: ${response.status} ${response.statusText}`);
}
return response.text();
}) })
.catch(error => console.error('Error loading config.json:', error)); .then((data) => {
// Split the log into lines
let lines = data.split('\n');
// Apply max lines limit if specified
if (maxLines > 0 && lines.length > maxLines) {
lines = lines.slice(-maxLines); // Get only the last N lines
} }
// Format log content
const formattedLog = lines
.map((line) => line.trim()) // Remove extra whitespace
.filter((line) => line) // Remove empty lines
.join('<br>'); // Join formatted lines with line breaks
// Display the formatted log
containerElement.innerHTML = `<pre style="white-space: pre-wrap; word-wrap: break-word; margin: 0;">${formattedLog}</pre>`;
// Scroll to bottom if requested
if (scrollToBottom) {
containerElement.scrollTop = containerElement.scrollHeight;
}
return formattedLog; // Return the formatted log in case the caller needs it
})
.catch((error) => {
console.error(`Error loading log file ${logFilePath}:`, error);
containerElement.innerHTML = `<div class="text-danger">Error loading log file: ${error.message}</div>`;
throw error; // Re-throw the error for the caller to handle if needed
});
}
/**
* Set up a refresh button for a log file
* @param {string} buttonId - ID of the button element
* @param {string} logFilePath - Path to the log file
* @param {string} containerId - ID of the container to display the log in
* @param {number} maxLines - Maximum number of lines to display (0 for all)
*/
function setupLogRefreshButton(buttonId, logFilePath, containerId, maxLines = 0) {
console.log("Refreshing logs");
const button = document.getElementById(buttonId);
const container = document.getElementById(containerId);
if (!button || !container) {
console.error('Button or container element not found');
return;
}
// Initial load
displayLogFile(logFilePath, container, true, maxLines);
// Set up button click handler
button.addEventListener('click', () => {
displayLogFile(logFilePath, container, true, maxLines);
});
}
function clear_loopLogs(){ function clear_loopLogs(){
console.log("Clearing loop logs"); console.log("Clearing loop logs");

View File

@@ -59,11 +59,12 @@
</div> </div>
<span id="modem_status_message"></span> <span id="modem_status_message"></span>
<!--
<h3> <h3>
Status Status
<span id="modem-status" class="badge">Loading...</span> <span id="modem-status" class="badge">Loading...</span>
</h3> </h3>
-->
<div class="row mb-3"> <div class="row mb-3">
<div class="col-sm-3"> <div class="col-sm-3">
@@ -71,7 +72,7 @@
<div class="card-body"> <div class="card-body">
<p class="card-text">General information. </p> <p class="card-text">General information. </p>
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'ATI', 2)">Get Data</button> <button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'ATI', 1)">Get Data</button>
<div id="loading_ttyAMA2_ATI" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div> <div id="loading_ttyAMA2_ATI" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_ATI"></div> <div id="response_ttyAMA2_ATI"></div>
@@ -84,7 +85,7 @@
<div class="card-body"> <div class="card-body">
<p class="card-text">SIM card information.</p> <p class="card-text">SIM card information.</p>
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CCID?', 2)">Get Data</button> <button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CCID?', 1)">Get Data</button>
<div id="loading_ttyAMA2_AT_CCID_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div> <div id="loading_ttyAMA2_AT_CCID_" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_CCID_"></div> <div id="response_ttyAMA2_AT_CCID_"></div>
</div> </div>
@@ -109,7 +110,7 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<p class="card-text">Signal strength </p> <p class="card-text">Signal strength </p>
<button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CSQ', 2)">Get Data</button> <button class="btn btn-primary" onclick="getData_saraR4('ttyAMA2', 'AT+CSQ', 1)">Get Data</button>
<div id="loading_ttyAMA2_AT_CSQ" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div> <div id="loading_ttyAMA2_AT_CSQ" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_CSQ"></div> <div id="response_ttyAMA2_AT_CSQ"></div>
</table> </table>
@@ -121,7 +122,7 @@
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<p class="card-text">Modem Reset </p> <p class="card-text">Modem Reset </p>
<button class="btn btn-danger" onclick="getData_saraR4('ttyAMA2', 'AT+CFUN=15', 2)">Reset</button> <button class="btn btn-danger" onclick="getData_saraR4('ttyAMA2', 'AT+CFUN=15', 1)">Reset</button>
<div id="loading_ttyAMA2_AT_CFUN_15" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div> <div id="loading_ttyAMA2_AT_CFUN_15" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<div id="response_ttyAMA2_AT_CFUN_15"></div> <div id="response_ttyAMA2_AT_CFUN_15"></div>
</table> </table>
@@ -305,6 +306,19 @@
</div> </div>
<!-- toast -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="liveToast" class="toast align-items-center text-bg-primary border-1" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
Hello, world! This is a toast message.
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</div>
</main> </main>
</div> </div>
</div> </div>
@@ -317,7 +331,7 @@
<script src="assets/js/bootstrap.bundle.js"></script> <script src="assets/js/bootstrap.bundle.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
const elementsToLoad = [ const elementsToLoad = [
{ id: 'topbar', file: 'topbar.html' }, { id: 'topbar', file: 'topbar.html' },
{ id: 'sidebar', file: 'sidebar.html' }, { id: 'sidebar', file: 'sidebar.html' },
@@ -336,6 +350,8 @@
.catch(error => console.error(`Error loading ${file}:`, error)); .catch(error => console.error(`Error loading ${file}:`, error));
}); });
//OLD way to retreive data from JSON
/*
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json' fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON .then(response => response.json()) // Parse response as JSON
.then(data => { .then(data => {
@@ -344,11 +360,89 @@
const check_modem_configMode = document.getElementById("check_modem_configMode"); const check_modem_configMode = document.getElementById("check_modem_configMode");
check_modem_configMode.checked = data.modem_config_mode; check_modem_configMode.checked = data.modem_config_mode;
console.log("Modem configuration: " + data.modem_config_mode); console.log("Modem configuration: " + data.modem_config_mode);
}) })
*/
//NEW way to get data from SQLITE
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//modem_version
const modem_version_html = document.getElementById("modem_version");
modem_version_html.innerText = response.modem_version;
// Set checkbox state based on the response data
const check_modem_configMode = document.getElementById("check_modem_configMode");
if (check_modem_configMode) {
check_modem_configMode.checked = response.modem_config_mode;
console.log("Modem configuration: " + response.modem_config_mode);
} else {
console.error("Checkbox element not found");
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}); });
window.onload = function() {
getModem_busy_status();
setInterval(getModem_busy_status, 1000);
//NEW way to get config (SQLite)
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function getData_saraR4(port, command, timeout){ function getData_saraR4(port, command, timeout){
console.log("Data from SaraR4"); console.log("Data from SaraR4");
console.log("Port: " + port ); console.log("Port: " + port );
@@ -430,8 +524,10 @@ function getData_saraR4(port, command, timeout){
} else{ } else{
// si c'est une commande AT normale // si c'est une commande AT normale
// Replace newline characters with <br> tags // Replace newline characters with <br> tags
const formattedResponse = response.replace(/\n/g, "<br>"); const formattedResponse = response.replace(/\n/g, "<br>")
.replace(/\b(OK)\b/g, '<span style="color: green; font-weight: bold;">$1</span>');;
$("#response_"+port+"_"+safeCommand).html(formattedResponse); $("#response_"+port+"_"+safeCommand).html(formattedResponse);
} }
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
@@ -700,84 +796,75 @@ function getModem_busy_status() {
} }
function update_modem_configMode(param, checked){ function update_modem_configMode(param, checked){
//change ('modem_config_mode', '0', 'bool') inside SQLITE db
// response type: {"success":true,"message":"Configuration updated successfully","param":"modem_config_mode","value":"0","type":"bool"}
const toastLiveExample = document.getElementById('liveToast')
const toastBody = toastLiveExample.querySelector('.toast-body');
console.log("updating modem config mode to :" + checked); console.log("updating modem config mode to :" + checked);
$.ajax({ $.ajax({
url: 'launcher.php?type=update_config&param='+param+'&value='+checked, url: 'launcher.php?type=update_config_sqlite&param='+param+'&value='+checked,
dataType: 'text', // Specify that you expect a JSON response dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs method: 'GET', // Use GET or POST depending on your needs
cache: false, // Prevent AJAX from caching cache: false, // Prevent AJAX from caching
success: function(response) { success: function(response) {
console.log("AJAX success:");
console.log(response); console.log(response);
// Format the response nicely
let formattedMessage = '';
if (response.success) {
// Success message
toastLiveExample.classList.remove('text-bg-danger');
toastLiveExample.classList.add('text-bg-success');
formattedMessage = `
<strong>Success!</strong><br>
Parameter: ${response.param || param}<br>
Value: ${response.value || checked}<br>
${response.message || ''}
`;
} else {
// Error message
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
formattedMessage = `
<strong>Error!</strong><br>
${response.error || 'Unknown error'}<br>
Parameter: ${response.param || param}
`;
}
// Update the toast body with formatted content
toastBody.innerHTML = formattedMessage;
// Show the toast
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
toastBootstrap.show()
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error); console.error('AJAX request failed:', status, error);
// Update toast with error message
toastBody.textContent = 'Error: ' + error;
// Set toast to danger color
toastLiveExample.classList.remove('text-bg-success');
toastLiveExample.classList.add('text-bg-danger');
// Show the toast for errors too
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
toastBootstrap.show();
} }
}); });
} }
window.onload = function() {
getModem_busy_status();
setInterval(getModem_busy_status, 1000);
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON
.then(data => {
//get device ID
const deviceID = data.deviceID.trim().toUpperCase();
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
//get device Name
const deviceName = data.deviceName;
//get modem version
const modem_version = data.modem_version;
const modem_version_html = document.getElementById("modem_version");
modem_version_html.textContent = modem_version;
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = deviceName;
});
//get SARA_R4 connection status
const SARA_statusElement = document.getElementById("modem-status");
console.log("SARA R4 is: " + data.SARA_R4_network_status);
if (data.SARA_R4_network_status === "connected") {
SARA_statusElement.textContent = "Connected";
SARA_statusElement.className = "badge text-bg-success";
} else if (data.SARA_R4_network_status === "disconnected") {
SARA_statusElement.textContent = "Disconnected";
SARA_statusElement.className = "badge text-bg-danger";
} else {
SARA_statusElement.textContent = "Unknown";
SARA_statusElement.className = "badge text-bg-secondary";
}
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
})
.catch(error => console.error('Error loading config.json:', error));
}
</script> </script>
</body> </body>

View File

@@ -144,27 +144,25 @@ function getNPM_values(port){
} }
function getENVEA_values(port, name){ function getENVEA_values(port, name){
console.log("Data from Envea "+ name+" (port "+port+"):"); console.log("Data from Envea " + name + " (port " + port + "):");
$("#loading_envea"+name).show(); $("#loading_envea" + name).show();
$.ajax({ $.ajax({
url: 'launcher.php?type=envea&port='+port+'&name='+name, url: 'launcher.php?type=envea&port=' + port + '&name=' + name,
dataType: 'json', // Specify that you expect a JSON response dataType: 'json',
method: 'GET', // Use GET or POST depending on your needs method: 'GET',
success: function(response) { success: function(response) {
console.log(response); console.log(response);
const tableBody = document.getElementById("data-table-body_envea"+name); const tableBody = document.getElementById("data-table-body_envea" + name);
tableBody.innerHTML = ""; tableBody.innerHTML = "";
$("#loading_envea"+name).hide(); $("#loading_envea" + name).hide();
// Create an array of the desired keys
// Create an array of the desired keys
const keysToShow = [name]; const keysToShow = [name];
// Add only the specified elements to the table
keysToShow.forEach(key => { keysToShow.forEach(key => {
if (response !== undefined) { // Check if the key exists in the response if (response !== undefined) {
const value = response; const value = response;
$("#data-table-body_envea"+name).append(` $("#data-table-body_envea" + name).append(`
<tr> <tr>
<td>${key}</td> <td>${key}</td>
<td>${value} ppb</td> <td>${value} ppb</td>
@@ -175,9 +173,21 @@ function getENVEA_values(port, name){
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error); console.error('AJAX request failed:', status, error);
const tableBody = document.getElementById("data-table-body_envea" + name);
$("#loading_envea" + name).hide();
tableBody.innerHTML = `
<tr>
<td colspan="2" class="text-danger">
❌ Error: unable to get data from sensor.<br>
<small>${status}: ${error}</small>
</td>
</tr>
`;
} }
}); });
} }
function getNoise_values(){ function getNoise_values(){
console.log("Data from I2C Noise Sensor:"); console.log("Data from I2C Noise Sensor:");
@@ -261,92 +271,68 @@ function getBME280_values(){
window.onload = function() { window.onload = function() {
fetch('../config.json') // Replace 'deviceID.txt' with 'config.json'
.then(response => response.json()) // Parse response as JSON
.then(data => {
//get device ID
const deviceID = data.deviceID.trim().toUpperCase();
//document.getElementById('pageTitle_plus_ID').innerText = 'token: ' + deviceID;
//get device Name
const deviceName = data.deviceName;
const elements = document.querySelectorAll('.sideBar_sensorName'); //NEW way to get config (SQLite)
elements.forEach((element) => { $.ajax({
element.innerText = deviceName; url: 'launcher.php?type=get_config_sqlite',
}); dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs method: 'GET', // Use GET or POST depending on your needs
success: function(response) { success: function(response) {
console.log("Local RTC: " + response); console.log("Getting SQLite config table:");
const RTC_Element = document.getElementById("RTC_time"); console.log(response);
RTC_Element.textContent = response;
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
}, },
error: function(xhr, status, error) { error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error); console.error('AJAX request failed:', status, error);
} }
}); });//end AJAX
//getting config_scripts table
$.ajax({
url: 'launcher.php?type=get_config_scripts_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config scripts table:");
console.log(response);
const container = document.getElementById('card-container'); // Conteneur des cartes const container = document.getElementById('card-container'); // Conteneur des cartes
//creates NPM cards //creates NPM card
const NPM_ports = data.NextPM_ports; // Récupère les ports if (response["NPM/get_data_modbus_v3.py"]) {
NPM_ports.forEach((port, index) => {
const cardHTML = ` const cardHTML = `
<div class="col-sm-3"> <div class="col-sm-3">
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
Port UART ${port.replace('ttyAMA', '')} Port UART
</div> </div>
<div class="card-body"> <div class="card-body">
<h5 class="card-title">NextPM ${String.fromCharCode(65 + index)}</h5> <h5 class="card-title">NextPM</h5>
<p class="card-text">Capteur particules fines.</p> <p class="card-text">Capteur particules fines.</p>
<button class="btn btn-primary" onclick="getNPM_values('${port}')">Get Data</button> <button class="btn btn-primary" onclick="getNPM_values('ttyAMA5')">Get Data</button>
<div id="loading_${port}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div> <br>
<div id="loading_ttyAMA5" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns"> <table class="table table-striped-columns">
<tbody id="data-table-body_${port}"></tbody> <tbody id="data-table-body_ttyAMA5"></tbody>
</table> </table>
</div> </div>
</div> </div>
</div>`; </div>`;
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
});
//creates ENVEA cards container.innerHTML += cardHTML; // Add the I2C card if condition is met
const ENVEA_sensors = data.envea_sondes.filter(sonde => sonde.connected); // Filter only connected sondes }
ENVEA_sensors.forEach((sensor, index) => {
const port = sensor.port; // Port from the sensor object
const name = sensor.name; // Port from the sensor object
const coefficient = sensor.coefficient;
const cardHTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
Port UART ${port.replace('ttyAMA', '')}
</div>
<div class="card-body">
<h5 class="card-title">Sonde Envea ${name}</h5>
<p class="card-text">Capteur gas.</p>
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}','${coefficient}')">Get Data</button>
<div id="loading_envea${name}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_envea${name}"></tbody>
</table>
</div>
</div>
</div>`;
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
});
//creates i2c BME280 card //creates i2c BME280 card
if (data["BME280/get_data_v2.py"]) { if (response["BME280/get_data_v2.py"]) {
const i2C_BME_HTML = ` const i2C_BME_HTML = `
<div class="col-sm-3"> <div class="col-sm-3">
<div class="card"> <div class="card">
@@ -370,7 +356,7 @@ window.onload = function() {
} }
//creates i2c sound card //creates i2c sound card
if (data.i2C_sound) { if (response.i2C_sound) {
const i2C_HTML = ` const i2C_HTML = `
<div class="col-sm-3"> <div class="col-sm-3">
<div class="card"> <div class="card">
@@ -395,9 +381,80 @@ window.onload = function() {
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
} }
}) //Si on a des SONDES ENVEA connectée il faut faire un deuxième call dans la table envea_sondes_table
.catch(error => console.error('Error loading config.json:', error)); //creates ENVEA cards
if (response["envea/read_value_v2.py"]) {
console.log("Need to display ENVEA sondes");
//getting config_scripts table
$.ajax({
url: 'launcher.php?type=get_envea_sondes_table_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(sondes) {
console.log("Getting SQLite envea sondes table:");
console.log(sondes);
const ENVEA_sensors = sondes.filter(sonde => sonde.connected); // Filter only connected sondes
ENVEA_sensors.forEach((sensor, index) => {
const port = sensor.port; // Port from the sensor object
const name = sensor.name; // Port from the sensor object
const coefficient = sensor.coefficient;
const cardHTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
Port UART ${port.replace('ttyAMA', '')}
</div>
<div class="card-body">
<h5 class="card-title">Sonde Envea ${name}</h5>
<p class="card-text">Capteur gas.</p>
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}')">Get Data</button>
<div id="loading_envea${name}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_envea${name}"></tbody>
</table>
</div>
</div>
</div>`;
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
} }
});//end AJAX envea Sondes
}//end if
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX (config_scripts)
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
} //end windows onload
</script> </script>
</body> </body>

View File

@@ -47,6 +47,12 @@
</svg> </svg>
Carte Carte
</a> </a>
<a class="nav-link text-white" href="terminal.html">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-terminal-fill" viewBox="0 0 16 16">
<path d="M0 3a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V3zm9.5 5.5h-3a.5.5 0 0 0 0 1h3a.5.5 0 0 0 0-1zm-6.354-.354a.5.5 0 1 0 .708.708l2-2a.5.5 0 0 0 0-.708l-2-2a.5.5 0 1 0-.708.708L4.793 6.5 3.146 8.146z"/>
</svg>
Terminal
</a>
<a class="nav-link text-white" href="admin.html"> <a class="nav-link text-white" href="admin.html">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tools" viewBox="0 0 16 16"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-tools" viewBox="0 0 16 16">
<path d="M1 0 0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.27 3.27a.997.997 0 0 0 1.414 0l1.586-1.586a.997.997 0 0 0 0-1.414l-3.27-3.27a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3q0-.405-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814zm9.646 10.646a.5.5 0 0 1 .708 0l2.914 2.915a.5.5 0 0 1-.707.707l-2.915-2.914a.5.5 0 0 1 0-.708M3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026z"/> <path d="M1 0 0 1l2.2 3.081a1 1 0 0 0 .815.419h.07a1 1 0 0 1 .708.293l2.675 2.675-2.617 2.654A3.003 3.003 0 0 0 0 13a3 3 0 1 0 5.878-.851l2.654-2.617.968.968-.305.914a1 1 0 0 0 .242 1.023l3.27 3.27a.997.997 0 0 0 1.414 0l1.586-1.586a.997.997 0 0 0 0-1.414l-3.27-3.27a1 1 0 0 0-1.023-.242L10.5 9.5l-.96-.96 2.68-2.643A3.005 3.005 0 0 0 16 3q0-.405-.102-.777l-2.14 2.141L12 4l-.364-1.757L13.777.102a3 3 0 0 0-3.675 3.68L7.462 6.46 4.793 3.793a1 1 0 0 1-.293-.707v-.071a1 1 0 0 0-.419-.814zm9.646 10.646a.5.5 0 0 1 .708 0l2.914 2.915a.5.5 0 0 1-.707.707l-2.915-2.914a.5.5 0 0 1 0-.708M3 11l.471.242.529.026.287.445.445.287.026.529L5 13l-.242.471-.026.529-.445.287-.287.445-.529.026L3 15l-.471-.242L2 14.732l-.287-.445L1.268 14l-.026-.529L1 13l.242-.471.026-.529.445-.287.287-.445.529-.026z"/>

413
html/terminal.html Normal file
View File

@@ -0,0 +1,413 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NebuleAir - Terminal</title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<style>
body {
overflow-x: hidden;
}
#sidebar a.nav-link {
position: relative;
display: flex;
align-items: center;
}
#sidebar a.nav-link:hover {
background-color: rgba(0, 0, 0, 0.5);
}
#sidebar a.nav-link svg {
margin-right: 8px; /* Add spacing between icons and text */
}
#sidebar {
transition: transform 0.3s ease-in-out;
}
.offcanvas-backdrop {
z-index: 1040;
}
#terminal {
width: 100%;
min-height: 400px;
max-height: 400px;
overflow-y: auto;
background-color: #000;
color: #00ff00;
font-family: monospace;
padding: 10px;
border-radius: 5px;
white-space: pre-wrap;
word-wrap: break-word;
}
#cmdLine {
width: 100%;
background-color: #000;
color: #00ff00;
font-family: monospace;
padding: 10px;
border: none;
border-radius: 0 0 5px 5px;
margin-top: -1px;
}
#cmdLine:focus {
outline: none;
}
.command-container {
display: none;
}
.password-popup {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
justify-content: center;
align-items: center;
}
.password-container {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
width: 300px;
}
.limited-commands {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
}
.limited-commands code {
white-space: nowrap;
margin-right: 10px;
margin-bottom: 5px;
display: inline-block;
}
</style>
</head>
<body>
<!-- Topbar -->
<span id="topbar"></span>
<!-- Sidebar Offcanvas for Mobile -->
<div class="offcanvas offcanvas-start text-white bg-dark" tabindex="-1" id="sidebarOffcanvas" aria-labelledby="sidebarOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="sidebarOffcanvasLabel">NebuleAir</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" id="sidebar_mobile">
</div>
</div>
<div class="container-fluid mt-5">
<div class="row">
<!-- Side bar -->
<aside class="col-md-2 col-lg-1 d-none d-md-block vh-100 position-fixed bg-dark text-white" id="sidebar">
</aside>
<!-- Main content -->
<main class="col-md-10 ms-sm-auto col-lg-11 offset-md-2 offset-lg-1 px-md-4">
<h1 class="mt-4">Terminal Console</h1>
<div class="row mb-3">
<div class="col-12">
<div class="alert alert-warning">
<strong>Warning:</strong> This terminal provides direct access to system commands.
Use with caution as improper commands may affect system functionality.
</div>
<div class="limited-commands">
<h5>Quick Commands:</h5>
<div>
<code onclick="insertCommand('ls -la')">ls -la</code>
<code onclick="insertCommand('df -h')">df -h</code>
<code onclick="insertCommand('free -h')">free -h</code>
<code onclick="insertCommand('uptime')">uptime</code>
<code onclick="insertCommand('systemctl status master_nebuleair.service')">service status</code>
<code onclick="insertCommand('cat /var/www/nebuleair_pro_4g/config.json')">view config</code>
</div>
</div>
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5 class="mb-0">Command Console</h5>
<div>
<button id="accessBtn" class="btn btn-primary me-2">Access Terminal</button>
<button id="clearBtn" class="btn btn-secondary" disabled>Clear</button>
</div>
</div>
<div class="card-body p-0">
<div class="command-container" id="commandContainer">
<div id="terminal">Welcome to NebuleAir Terminal Console
Type your commands below. Type 'help' for a list of commands.
</div>
<input type="text" id="cmdLine" placeholder="Enter command..." disabled>
</div>
<div id="errorMsg" class="alert alert-danger m-3" style="display:none;"></div>
</div>
</div>
</div>
</div>
</main>
</div>
</div>
<!-- Password Modal -->
<div class="password-popup" id="passwordModal">
<div class="password-container">
<h5>Authentication Required</h5>
<p>Please enter the admin password to access the terminal:</p>
<div class="mb-3">
<input type="password" class="form-control" id="adminPassword" placeholder="Password">
</div>
<div class="mb-3 d-flex justify-content-between">
<button class="btn btn-secondary" id="cancelPasswordBtn">Cancel</button>
<button class="btn btn-primary" id="submitPasswordBtn">Submit</button>
</div>
<div id="passwordError" class="text-danger mt-2" style="display:none;"></div>
</div>
</div>
<!-- JAVASCRIPT -->
<!-- Link Ajax locally -->
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
<!-- Link Bootstrap JS and Popper.js locally -->
<script src="assets/js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const elementsToLoad = [
{ id: 'topbar', file: 'topbar.html' },
{ id: 'sidebar', file: 'sidebar.html' },
{ id: 'sidebar_mobile', file: 'sidebar.html' }
];
elementsToLoad.forEach(({ id, file }) => {
fetch(file)
.then(response => response.text())
.then(data => {
const element = document.getElementById(id);
if (element) {
element.innerHTML = data;
}
})
.catch(error => console.error(`Error loading ${file}:`, error));
});
// Initialize elements
initializeElements();
});
window.onload = function() {
//NEW way to get config (SQLite)
$.ajax({
url: 'launcher.php?type=get_config_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Getting SQLite config table:");
console.log(response);
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
}
// Add admin password (should be changed to something more secure)
const ADMIN_PASSWORD = "123plouf";
// Global variables
let terminal;
let cmdLine;
let commandContainer;
let accessBtn;
let clearBtn;
let passwordModal;
let adminPassword;
let submitPasswordBtn;
let cancelPasswordBtn;
let passwordError;
let errorMsg;
let commandHistory = [];
let historyIndex = -1;
// Initialize DOM references after document is loaded
function initializeElements() {
terminal = document.getElementById('terminal');
cmdLine = document.getElementById('cmdLine');
commandContainer = document.getElementById('commandContainer');
accessBtn = document.getElementById('accessBtn');
clearBtn = document.getElementById('clearBtn');
passwordModal = document.getElementById('passwordModal');
adminPassword = document.getElementById('adminPassword');
submitPasswordBtn = document.getElementById('submitPasswordBtn');
cancelPasswordBtn = document.getElementById('cancelPasswordBtn');
passwordError = document.getElementById('passwordError');
errorMsg = document.getElementById('errorMsg');
// Set up event listeners
accessBtn.addEventListener('click', function() {
passwordModal.style.display = 'flex';
adminPassword.value = ''; // Clear password field
passwordError.style.display = 'none';
adminPassword.focus();
});
// Password submit button
submitPasswordBtn.addEventListener('click', function() {
if (adminPassword.value === ADMIN_PASSWORD) {
passwordModal.style.display = 'none';
enableTerminal();
} else {
passwordError.textContent = 'Invalid password';
passwordError.style.display = 'block';
}
});
// Enter key for password
adminPassword.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
submitPasswordBtn.click();
}
});
// Cancel password button
cancelPasswordBtn.addEventListener('click', function() {
passwordModal.style.display = 'none';
});
// Clear button
clearBtn.addEventListener('click', function() {
terminal.innerHTML = 'Terminal cleared.\n';
});
// Command line input events
cmdLine.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
const command = cmdLine.value.trim();
if (command) {
executeCommand(command);
commandHistory.push(command);
historyIndex = commandHistory.length;
cmdLine.value = '';
}
}
});
// Command history navigation with arrow keys
cmdLine.addEventListener('keydown', function(e) {
if (e.key === 'ArrowUp') {
if (historyIndex > 0) {
historyIndex--;
cmdLine.value = commandHistory[historyIndex];
}
e.preventDefault();
} else if (e.key === 'ArrowDown') {
if (historyIndex < commandHistory.length - 1) {
historyIndex++;
cmdLine.value = commandHistory[historyIndex];
} else {
historyIndex = commandHistory.length;
cmdLine.value = '';
}
e.preventDefault();
}
});
}
// Enable terminal access
function enableTerminal() {
commandContainer.style.display = 'block';
cmdLine.disabled = false;
clearBtn.disabled = false;
accessBtn.textContent = 'Authenticated';
accessBtn.classList.remove('btn-primary');
accessBtn.classList.add('btn-success');
accessBtn.disabled = true;
cmdLine.focus();
}
// Insert a predefined command
function insertCommand(cmd) {
// Only allow insertion if terminal is enabled
if (cmdLine.disabled === false) {
cmdLine.value = cmd;
cmdLine.focus();
} else {
// Alert user that they need to authenticate first
alert('Please access the terminal first by clicking "Access Terminal" and entering the password.');
}
}
// Execute a command
function executeCommand(command) {
// Add command to terminal with user prefix
terminal.innerHTML += `<span style="color: cyan;">user@nebuleair</span>:<span style="color: yellow;">~</span>$ ${command}\n`;
terminal.scrollTop = terminal.scrollHeight;
// Handle special commands
if (command === 'clear') {
terminal.innerHTML = 'Terminal cleared.\n';
return;
}
// Filter dangerous commands
const dangerousCommands = [
'rm -rf /', 'rm -rf /*', 'rm -rf ~', 'rm -rf ~/*',
'mkfs', 'dd if=/dev/zero', 'dd if=/dev/random',
'>>', '>', '|', ';', '&&', '||',
'wget', 'curl', 'ssh', 'scp', 'nc',
'chmod -R', 'chown -R'
];
// Check for dangerous commands or command chaining
const hasDangerousCommand = dangerousCommands.some(cmd => command.includes(cmd));
if (hasDangerousCommand || command.includes('&') || command.includes(';') || command.includes('|')) {
terminal.innerHTML += '<span style="color: red;">Error: This command is not allowed for security reasons.</span>\n';
terminal.scrollTop = terminal.scrollHeight;
return;
}
// Execute the command via AJAX
$.ajax({
url: 'launcher.php?type=execute_command',
method: 'POST',
dataType:'json',
data: {
type: 'execute_command',
command: command
},
success: function(response) {
console.log(response);
if (response.success) {
// Add command output to terminal
terminal.innerHTML += `<span style="color: #00ff00;">${response.output}</span>\n`;
} else {
terminal.innerHTML += `<span style="color: red;">Error: ${response.message}</span>\n`;
}
terminal.scrollTop = terminal.scrollHeight;
},
error: function(xhr, status, error) {
terminal.innerHTML += `<span style="color: red;">Error executing command: ${error}</span>\n`;
terminal.scrollTop = terminal.scrollHeight;
}
});
}
</script>
</body>
</html>

View File

@@ -302,6 +302,11 @@ function get_internet(){
element.innerText = deviceName; element.innerText = deviceName;
}); });
//device name html page title
if (response.deviceName) {
document.title = response.deviceName;
}
//get wifi connection status //get wifi connection status
const WIFI_statusElement = document.getElementById("wifi-status"); const WIFI_statusElement = document.getElementById("wifi-status");

View File

@@ -23,40 +23,27 @@ fi
# Update and install necessary packages # Update and install necessary packages
info "Updating package list and installing necessary packages..." info "Updating package list and installing necessary packages..."
sudo apt update && sudo apt install -y git gh apache2 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus || error "Failed to install required packages." sudo apt update && sudo apt install -y git gh apache2 sqlite3 php php-sqlite3 python3 python3-pip jq autossh i2c-tools python3-smbus || error "Failed to install required packages."
# Install Python libraries # Install Python libraries
info "Installing Python libraries..." info "Installing Python libraries..."
sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil ntplib pytz --break-system-packages || error "Failed to install Python libraries." sudo pip3 install pyserial requests RPi.GPIO adafruit-circuitpython-bme280 crcmod psutil gpiozero ntplib pytz --break-system-packages || error "Failed to install Python libraries."
# Ask user if they want to set up SSH keys
read -p "Do you want to set up an SSH key for /var/www? (y/n): " answer
answer=${answer,,} # Convert to lowercase
if [[ "$answer" == "y" ]]; then
info "Setting up SSH keys..."
sudo mkdir -p /var/www/.ssh
sudo chmod 700 /var/www/.ssh
if [[ ! -f /var/www/.ssh/id_rsa ]]; then
sudo ssh-keygen -t rsa -b 4096 -f /var/www/.ssh/id_rsa -N ""
success "SSH key generated successfully."
else
warning "SSH key already exists. Skipping key generation."
fi
sudo ssh-copy-id -i /var/www/.ssh/id_rsa.pub -p 50221 airlab_server1@aircarto.fr || warning "Failed to copy SSH key. Please check the server connection."
success "SSH setup complete!"
else
warning "Skipping SSH key setup."
fi
# Clone the repository (check if it exists first) # Clone the repository (check if it exists first)
REPO_DIR="/var/www/nebuleair_pro_4g" REPO_DIR="/var/www/nebuleair_pro_4g"
if [[ -d "$REPO_DIR" ]]; then if [[ -d "$REPO_DIR" ]]; then
warning "Repository already exists. Skipping clone." warning "Repository already exists. Will update instead of clone."
# Save current directory
local current_dir=$(pwd)
# Navigate to repository directory
cd "$REPO_DIR"
# Stash any local changes
sudo git stash || warning "Failed to stash local changes"
# Pull latest changes
sudo git pull || error "Failed to pull latest changes"
# Return to original directory
cd "$current_dir"
success "Repository updated successfully!"
else else
info "Cloning the NebuleAir Pro 4G repository..." info "Cloning the NebuleAir Pro 4G repository..."
sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git "$REPO_DIR" || error "Failed to clone repository." sudo git clone http://gitea.aircarto.fr/PaulVua/nebuleair_pro_4g.git "$REPO_DIR" || error "Failed to clone repository."
@@ -66,7 +53,6 @@ fi
info "Setting up repository files and permissions..." info "Setting up repository files and permissions..."
sudo mkdir -p "$REPO_DIR/logs" sudo mkdir -p "$REPO_DIR/logs"
sudo touch "$REPO_DIR/logs/app.log" "$REPO_DIR/logs/master.log" "$REPO_DIR/logs/master_errors.log" "$REPO_DIR/wifi_list.csv" sudo touch "$REPO_DIR/logs/app.log" "$REPO_DIR/logs/master.log" "$REPO_DIR/logs/master_errors.log" "$REPO_DIR/wifi_list.csv"
sudo cp "$REPO_DIR/config.json.dist" "$REPO_DIR/config.json"
sudo chmod -R 755 "$REPO_DIR/" sudo chmod -R 755 "$REPO_DIR/"
sudo chown -R www-data:www-data "$REPO_DIR/" sudo chown -R www-data:www-data "$REPO_DIR/"
sudo git config --global core.fileMode false sudo git config --global core.fileMode false
@@ -91,6 +77,15 @@ else
warning "Database creation script not found." warning "Database creation script not found."
fi fi
# Set config
info "Set config..."
if [[ -f "$REPO_DIR/sqlite/set_config.py" ]]; then
sudo /usr/bin/python3 "$REPO_DIR/sqlite/set_config.py" || error "Failed to set config."
success "Databases created successfully."
else
warning "Database creation script not found."
fi
# Configure Apache # Configure Apache
info "Configuring Apache..." info "Configuring Apache..."
APACHE_CONF="/etc/apache2/sites-available/000-default.conf" APACHE_CONF="/etc/apache2/sites-available/000-default.conf"
@@ -105,7 +100,7 @@ fi
# Add sudo authorization (prevent duplicate entries) # Add sudo authorization (prevent duplicate entries)
info "Setting up sudo authorization..." info "Setting up sudo authorization..."
if ! sudo grep -q "/usr/bin/nmcli" /etc/sudoers; then if ! sudo grep -q "/usr/bin/nmcli" /etc/sudoers; then
echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 *" | sudo tee -a /etc/sudoers > /dev/null echo -e "ALL ALL=(ALL) NOPASSWD: /usr/bin/nmcli, /usr/sbin/reboot\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/git pull\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/ssh\nwww-data ALL=(ALL) NOPASSWD: /usr/bin/python3 * www-data ALL=(ALL) NOPASSWD: /bin/systemctl * www-data ALL=(ALL) NOPASSWD: /var/www/nebuleair_pro_4g/*" | sudo tee -a /etc/sudoers > /dev/null
success "Sudo authorization added." success "Sudo authorization added."
else else
warning "Sudo authorization already set. Skipping." warning "Sudo authorization already set. Skipping."
@@ -133,7 +128,6 @@ success "I2C ports enabled."
info "Creates sqlites databases..." info "Creates sqlites databases..."
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py /usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
# Completion message # Completion message
success "Setup completed successfully!" success "Setup completed successfully!"
info "System will reboot in 5 seconds..." info "System will reboot in 5 seconds..."

View File

@@ -22,13 +22,19 @@ error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
if [[ "$EUID" -ne 0 ]]; then if [[ "$EUID" -ne 0 ]]; then
error "This script must be run as root. Use 'sudo ./installation.sh'" error "This script must be run as root. Use 'sudo ./installation.sh'"
fi fi
REPO_DIR="/var/www/nebuleair_pro_4g"
#set up the RTC #set up the RTC
info "Set up the RTC" info "Set up the RTC"
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
#Check SARA R4 connection #Wake up SARA
info "Check SARA R4 connection" info "Wake Up SARA"
pinctrl set 16 op
pinctrl set 16 dh
sleep 5
#Check SARA connection
info "Check SARA connection"
/usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 ATI 2 /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara.py ttyAMA2 ATI 2
#set up SARA R4 APN #set up SARA R4 APN
@@ -44,45 +50,19 @@ info "Connect SARA R4 to network"
python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 20810 60 python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ttyAMA2 20810 60
#Need to create the two service #Need to create the two service
# 1. master_nebuleair # 1. start the scripts to set-up the services
# 2. rtc_save_to_db # 2. rtc_save_to_db
#1. Add master_nebuleair.service #1. set-up the services (SARA, NPM, BME280, etc)
SERVICE_FILE="/etc/systemd/system/master_nebuleair.service" info "Setting up systemd services..."
info "Setting up systemd service for master_nebuleair..."
# Create the systemd service file (overwrite if necessary)
sudo bash -c "cat > $SERVICE_FILE" <<EOF
[Unit]
Description=Master manager for the Python loop scripts
After=network.target
[Service]
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/master.py
Restart=always
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/master.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/master_errors.log
[Install]
WantedBy=multi-user.target
EOF
success "Systemd service file created: $SERVICE_FILE"
# Reload systemd to recognize the new service
info "Reloading systemd daemon..."
sudo systemctl daemon-reload
# Enable the service to start on boot
info "Enabling the service to start on boot..."
sudo systemctl enable master_nebuleair.service
# Start the service immediately
info "Starting the service..."
sudo systemctl start master_nebuleair.service
if [[ -f "$REPO_DIR/services/setup_services.sh" ]]; then
sudo chmod +x "$REPO_DIR/services/setup_services.sh"
sudo "$REPO_DIR/services/setup_services.sh" || warning "Failed to set up systemd services"
success "Systemd services set up successfully."
else
warning "Systemd services setup script not found."
fi
#2. Add rtc_save_to_db.service #2. Add rtc_save_to_db.service
SERVICE_FILE_2="/etc/systemd/system/rtc_save_to_db.service" SERVICE_FILE_2="/etc/systemd/system/rtc_save_to_db.service"

File diff suppressed because it is too large Load Diff

0
config.json.dist → old/config.json.dist Executable file → Normal file
View File

0
install_software.yaml → old/install_software.yaml Executable file → Normal file
View File

View File

@@ -52,17 +52,73 @@ Specific scripts can be disabled with config.json
import time import time
import threading import threading
import subprocess import subprocess
import json
import os import os
import sqlite3
# Base directory where scripts are stored # Base directory where scripts are stored
SCRIPT_DIR = "/var/www/nebuleair_pro_4g/" SCRIPT_DIR = "/var/www/nebuleair_pro_4g/"
CONFIG_FILE = "/var/www/nebuleair_pro_4g/config.json" DB_PATH = "/var/www/nebuleair_pro_4g/sqlite/sensors.db"
# Lock file path for SARA script
SARA_LOCK_FILE = "/var/www/nebuleair_pro_4g/sara_script.lock"
def is_script_enabled(script_name):
"""Check if a script is enabled in the database."""
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(
"SELECT enabled FROM config_scripts_table WHERE script_path = ?",
(script_name,)
)
result = cursor.fetchone()
conn.close()
if result is None:
return True # Default to enabled if not found in database
return bool(result[0])
except Exception:
# If any database error occurs, default to enabled
return True
def create_lock_file():
"""Create a lock file for the SARA script."""
with open(SARA_LOCK_FILE, 'w') as f:
f.write(str(int(time.time())))
def remove_lock_file():
"""Remove the SARA script lock file."""
if os.path.exists(SARA_LOCK_FILE):
os.remove(SARA_LOCK_FILE)
def is_script_locked():
"""Check if the SARA script is currently locked."""
if not os.path.exists(SARA_LOCK_FILE):
return False
# Check if lock is older than 60 seconds (stale)
with open(SARA_LOCK_FILE, 'r') as f:
try:
lock_time = int(f.read().strip())
if time.time() - lock_time > 60:
# Lock is stale, remove it
remove_lock_file()
return False
except ValueError:
# Invalid lock file content
remove_lock_file()
return False
return True
def load_config():
"""Load the configuration file to determine which scripts to run."""
with open(CONFIG_FILE, "r") as f:
return json.load(f)
def run_script(script_name, interval, delay=0): def run_script(script_name, interval, delay=0):
"""Run a script in a synchronized loop with an optional start delay.""" """Run a script in a synchronized loop with an optional start delay."""
@@ -70,8 +126,17 @@ def run_script(script_name, interval, delay=0):
next_run = time.monotonic() + delay # Apply the initial delay next_run = time.monotonic() + delay # Apply the initial delay
while True: while True:
config = load_config() if is_script_enabled(script_name):
if config.get(script_name, True): # Default to True if not found # Special handling for SARA script to prevent concurrent runs
if script_name == "loop/SARA_send_data_v2.py":
if not is_script_locked():
create_lock_file()
try:
subprocess.run(["python3", script_path], timeout=200)
finally:
remove_lock_file()
else:
# Run other scripts normally
subprocess.run(["python3", script_path]) subprocess.run(["python3", script_path])
# Wait until the next exact interval # Wait until the next exact interval
@@ -79,19 +144,19 @@ def run_script(script_name, interval, delay=0):
sleep_time = max(0, next_run - time.monotonic()) # Prevent negative sleep times sleep_time = max(0, next_run - time.monotonic()) # Prevent negative sleep times
time.sleep(sleep_time) time.sleep(sleep_time)
# Define scripts and their execution intervals (seconds) # Define scripts and their execution intervals (seconds)
SCRIPTS = [ SCRIPTS = [
#("RTC/save_to_db.py", 1, 0), # --> will run as a separated system service (rtc_save_to_db.service) # Format: (script_name, interval_in_seconds, start_delay_in_seconds)
("NPM/get_data_modbus_v3.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s, with 2s delay ("NPM/get_data_modbus_v3.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s
("envea/read_value_v2.py", 10, 0), # Get NPM data (modbus 5 channels) every 10s, with 2s delay ("envea/read_value_v2.py", 10, 0), # Get Envea data every 10s
("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 2s delay ("loop/SARA_send_data_v2.py", 60, 1), # Send data every 60 seconds, with 1s delay
("BME280/get_data_v2.py", 120, 0), # Get BME280 data every 120 seconds, no delay ("BME280/get_data_v2.py", 120, 0), # Get BME280 data every 120 seconds
("MPPT/read.py", 120, 0), # Get MPPT data every 120 seconds, no delay ("MPPT/read.py", 120, 0), # Get MPPT data every 120 seconds
#("windMeter/read.py", 60, 2), # --> will run as a separated system service () ("sqlite/flush_old_data.py", 86400, 0) # Flush old data inside db every day
("sqlite/flush_old_data.py", 86400, 0) # flush old data inside db every day ()
] ]
# Start threads for enabled scripts # Start threads for scripts
for script_name, interval, delay in SCRIPTS: for script_name, interval, delay in SCRIPTS:
thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True) thread = threading.Thread(target=run_script, args=(script_name, interval, delay), daemon=True)
thread.start() thread.start()
@@ -99,4 +164,3 @@ for script_name, interval, delay in SCRIPTS:
# Keep the main script running # Keep the main script running
while True: while True:
time.sleep(1) time.sleep(1)

18
services/README.md Normal file
View File

@@ -0,0 +1,18 @@
# NebuleAir Pro Services
Les scripts importants tournent à l'aide d'un service et d'un timer associé.
Pour les installer:
sudo chmod +x /var/www/nebuleair_pro_4g/services/setup_services.sh
sudo /var/www/nebuleair_pro_4g/services/setup_services.sh
Supprimer l'ancien master:
sudo systemctl stop master_nebuleair.service
sudo systemctl disable master_nebuleair.service
# Check les services
SARA:
sudo systemctl status nebuleair-sara-data.service

View File

@@ -0,0 +1,39 @@
#!/bin/bash
# File: /var/www/nebuleair_pro_4g/services/check_services.sh
# Purpose: Check status of all NebuleAir services and logs
# Install:
# sudo chmod +x /var/www/nebuleair_pro_4g/services/check_services.sh
# sudo /var/www/nebuleair_pro_4g/services/check_services.sh
echo "=== NebuleAir Services Status ==="
echo ""
# Check status of all timers
echo "--- TIMER STATUS ---"
systemctl list-timers | grep nebuleair
echo ""
# Check status of all services
echo "--- SERVICE STATUS ---"
for service in npm envea sara bme280 mppt db-cleanup; do
status=$(systemctl is-active nebuleair-$service-data.service)
timer_status=$(systemctl is-active nebuleair-$service-data.timer)
echo "nebuleair-$service-data: Service=$status, Timer=$timer_status"
done
echo ""
# Show recent logs for each service
echo "--- RECENT LOGS (last 5 entries per service) ---"
for service in npm envea sara bme280 mppt db-cleanup; do
echo "[$service service logs]"
journalctl -u nebuleair-$service-data.service -n 5 --no-pager
echo ""
done
echo "=== End of Report ==="
echo ""
echo "For detailed logs use:"
echo " sudo journalctl -u nebuleair-[service]-data.service -f"
echo "To restart a specific service timer:"
echo " sudo systemctl restart nebuleair-[service]-data.timer"

228
services/setup_services.sh Normal file
View File

@@ -0,0 +1,228 @@
#!/bin/bash
# File: /var/www/nebuleair_pro_4g/services/setup_services.sh
# Purpose: Set up all systemd services for NebuleAir data collection
# to install:
# sudo chmod +x /var/www/nebuleair_pro_4g/services/setup_services.sh
# sudo /var/www/nebuleair_pro_4g/services/setup_services.sh
echo "Setting up NebuleAir systemd services and timers..."
# Create directory for logs if it doesn't exist
mkdir -p /var/www/nebuleair_pro_4g/logs
# Create service and timer files for NPM Data
cat > /etc/systemd/system/nebuleair-npm-data.service << 'EOL'
[Unit]
Description=NebuleAir NPM Data Collection Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/NPM/get_data_modbus_v3.py
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/npm_service.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/npm_service_errors.log
[Install]
WantedBy=multi-user.target
EOL
cat > /etc/systemd/system/nebuleair-npm-data.timer << 'EOL'
[Unit]
Description=Run NebuleAir NPM Data Collection every 10 seconds
Requires=nebuleair-npm-data.service
[Timer]
OnBootSec=10s
OnUnitActiveSec=10s
AccuracySec=1s
[Install]
WantedBy=timers.target
EOL
# Create service and timer files for Envea Data
cat > /etc/systemd/system/nebuleair-envea-data.service << 'EOL'
[Unit]
Description=NebuleAir Envea Data Collection Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/envea/read_value_v2.py
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/envea_service.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/envea_service_errors.log
[Install]
WantedBy=multi-user.target
EOL
cat > /etc/systemd/system/nebuleair-envea-data.timer << 'EOL'
[Unit]
Description=Run NebuleAir Envea Data Collection every 10 seconds
Requires=nebuleair-envea-data.service
[Timer]
OnBootSec=10s
OnUnitActiveSec=10s
AccuracySec=1s
[Install]
WantedBy=timers.target
EOL
# Create service and timer files for SARA Data (No Lock File Needed)
cat > /etc/systemd/system/nebuleair-sara-data.service << 'EOL'
[Unit]
Description=NebuleAir SARA Data Transmission Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/loop/SARA_send_data_v2.py
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/sara_service.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/sara_service_errors.log
RuntimeMaxSec=200s
[Install]
WantedBy=multi-user.target
EOL
cat > /etc/systemd/system/nebuleair-sara-data.timer << 'EOL'
[Unit]
Description=Run NebuleAir SARA Data Transmission every 60 seconds
Requires=nebuleair-sara-data.service
[Timer]
OnBootSec=60s
OnUnitActiveSec=60s
AccuracySec=1s
# This is the key setting that prevents overlap
Persistent=true
[Install]
WantedBy=timers.target
EOL
# Create service and timer files for BME280 Data
cat > /etc/systemd/system/nebuleair-bme280-data.service << 'EOL'
[Unit]
Description=NebuleAir BME280 Data Collection Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/BME280/get_data_v2.py
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/bme280_service.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/bme280_service_errors.log
[Install]
WantedBy=multi-user.target
EOL
cat > /etc/systemd/system/nebuleair-bme280-data.timer << 'EOL'
[Unit]
Description=Run NebuleAir BME280 Data Collection every 120 seconds
Requires=nebuleair-bme280-data.service
[Timer]
OnBootSec=120s
OnUnitActiveSec=120s
AccuracySec=1s
[Install]
WantedBy=timers.target
EOL
# Create service and timer files for MPPT Data
cat > /etc/systemd/system/nebuleair-mppt-data.service << 'EOL'
[Unit]
Description=NebuleAir MPPT Data Collection Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/MPPT/read.py
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/mppt_service.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/mppt_service_errors.log
[Install]
WantedBy=multi-user.target
EOL
cat > /etc/systemd/system/nebuleair-mppt-data.timer << 'EOL'
[Unit]
Description=Run NebuleAir MPPT Data Collection every 120 seconds
Requires=nebuleair-mppt-data.service
[Timer]
OnBootSec=120s
OnUnitActiveSec=120s
AccuracySec=1s
[Install]
WantedBy=timers.target
EOL
# Create service and timer files for Database Cleanup
cat > /etc/systemd/system/nebuleair-db-cleanup-data.service << 'EOL'
[Unit]
Description=NebuleAir Database Cleanup Service
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/flush_old_data.py
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/db_cleanup_service.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/db_cleanup_service_errors.log
[Install]
WantedBy=multi-user.target
EOL
cat > /etc/systemd/system/nebuleair-db-cleanup-data.timer << 'EOL'
[Unit]
Description=Run NebuleAir Database Cleanup daily
Requires=nebuleair-db-cleanup-data.service
[Timer]
OnBootSec=1h
OnUnitActiveSec=24h
AccuracySec=1h
[Install]
WantedBy=timers.target
EOL
# 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 mppt db-cleanup; do
systemctl enable nebuleair-$service-data.timer
systemctl start nebuleair-$service-data.timer
echo "Started nebuleair-$service-data timer"
done
echo "Checking status of all timers..."
systemctl list-timers | grep nebuleair
echo "Setup complete. All NebuleAir services are now running."
echo "To check the status of a specific service:"
echo " sudo systemctl status nebuleair-npm-data.service"
echo "To view logs for a specific service:"
echo " sudo journalctl -u nebuleair-npm-data.service"
echo "To restart a specific timer:"
echo " sudo systemctl restart nebuleair-npm-data.timer"

View File

@@ -18,6 +18,26 @@ import sqlite3
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db") conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor() cursor = conn.cursor()
#create a config table
cursor.execute("""
CREATE TABLE IF NOT EXISTS config_table (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
type TEXT NOT NULL
)
""")
#creates a config table for envea sondes
cursor.execute("""
CREATE TABLE IF NOT EXISTS envea_sondes_table (
id INTEGER PRIMARY KEY AUTOINCREMENT,
connected INTEGER NOT NULL,
port TEXT NOT NULL,
name TEXT NOT NULL,
coefficient REAL NOT NULL
)
""")
# Create a table timer # Create a table timer
cursor.execute(""" cursor.execute("""
CREATE TABLE IF NOT EXISTS timestamp_table ( CREATE TABLE IF NOT EXISTS timestamp_table (

View File

@@ -45,7 +45,7 @@ if row:
print(f"[INFO] Deleting records older than: {cutoff_date_str}") print(f"[INFO] Deleting records older than: {cutoff_date_str}")
# List of tables to delete old data from # List of tables to delete old data from
tables_to_clean = ["data_NPM", "data_NPM_5channels", "data_BME280", "data_envea"] tables_to_clean = ["data_NPM", "data_NPM_5channels", "data_BME280", "data_envea","data_WIND", "data_MPPT"]
# Loop through each table and delete old data # Loop through each table and delete old data
for table in tables_to_clean: for table in tables_to_clean:

View File

@@ -36,7 +36,6 @@ cursor = conn.cursor()
#cursor.execute("SELECT * FROM timestamp_table") #cursor.execute("SELECT * FROM timestamp_table")
if table_name == "timestamp_table": if table_name == "timestamp_table":
cursor.execute("SELECT * FROM timestamp_table") cursor.execute("SELECT * FROM timestamp_table")
else: else:
query = f"SELECT * FROM {table_name} ORDER BY timestamp DESC LIMIT ?" query = f"SELECT * FROM {table_name} ORDER BY timestamp DESC LIMIT ?"
cursor.execute(query, (limit_num,)) cursor.execute(query, (limit_num,))

43
sqlite/read_config.py Normal file
View File

@@ -0,0 +1,43 @@
'''
____ ___ _ _ _
/ ___| / _ \| | (_) |_ ___
\___ \| | | | | | | __/ _ \
___) | |_| | |___| | || __/
|____/ \__\_\_____|_|\__\___|
Script to read data from a sqlite database
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/read_config.py config_table
Available table are
config_table
config_scripts_table
envea_sondes_table
'''
import sqlite3
import sys
parameter = sys.argv[1:] # Exclude the script name
#print("Parameters received:")
table_name=parameter[0]
# Connect to the SQLite database
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
# Retrieve the data
query = f"SELECT * FROM {table_name}"
cursor.execute(query)
rows = cursor.fetchall()
rows.reverse() # Reverse the order in Python (to get ascending order)
# Display the results
for row in rows:
print(row)
# Close the database connection
conn.close()

77
sqlite/set_config.py Normal file
View File

@@ -0,0 +1,77 @@
'''
____ ___ _ _ _
/ ___| / _ \| | (_) |_ ___
\___ \| | | | | | | __/ _ \
___) | |_| | |___| | || __/
|____/ \__\_\_____|_|\__\___|
Script to set the config
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
in case of readonly error:
sudo chmod 777 /var/www/nebuleair_pro_4g/sqlite/sensors.db
'''
import sqlite3
# Connect to (or create if not existent) the database
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
print(f"Connected to database")
# Note: Using INSERT OR IGNORE to add only new configurations without overwriting existing ones
print("Adding new configurations (existing ones will be preserved)")
# Insert general configurations
config_entries = [
("modem_config_mode", "0", "bool"),
("deviceID", "XXXX", "str"),
("latitude_raw", "0", "int"),
("longitude_raw", "0", "int"),
("latitude_precision", "0", "int"),
("longitude_precision", "0", "int"),
("deviceName", "NebuleAir-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"),
("npm_5channel", "0", "bool"),
("envea", "0", "bool"),
("windMeter", "0", "bool"),
("BME280", "0", "bool"),
("MPPT", "0", "bool"),
("modem_version", "XXX", "str")
]
for key, value, value_type in config_entries:
cursor.execute(
"INSERT OR IGNORE INTO config_table (key, value, type) VALUES (?, ?, ?)",
(key, value, value_type)
)
# Insert envea sondes
envea_sondes = [
(False, "ttyAMA4", "h2s", 4),
(False, "ttyAMA3", "no2", 1),
(False, "ttyAMA2", "o3", 1)
]
for connected, port, name, coefficient in envea_sondes:
cursor.execute(
"INSERT OR IGNORE INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
(1 if connected else 0, port, name, coefficient)
)
# Commit and close the connection
conn.commit()
conn.close()
print("Database updated successfully!")

118
update_firmware.sh Normal file
View File

@@ -0,0 +1,118 @@
#!/bin/bash
# NebuleAir Pro 4G - Comprehensive Update Script
# This script performs a complete system update including git pull,
# config initialization, and service management
echo "======================================"
echo "NebuleAir Pro 4G - Firmware Update"
echo "======================================"
echo "Started at: $(date)"
echo ""
# Set working directory
cd /var/www/nebuleair_pro_4g
# Function to print status messages
print_status() {
echo "[$(date '+%H:%M:%S')] $1"
}
# Function to check command success
check_status() {
if [ $? -eq 0 ]; then
print_status "$1 completed successfully"
else
print_status "$1 failed"
return 1
fi
}
# Step 1: Git operations
print_status "Step 1: Updating firmware from repository..."
git fetch origin
check_status "Git fetch"
# Show current branch and any changes
print_status "Current branch: $(git branch --show-current)"
if [ -n "$(git status --porcelain)" ]; then
print_status "Warning: Local changes detected:"
git status --short
fi
# Pull latest changes
git pull origin $(git branch --show-current)
check_status "Git pull"
# Step 2: Update database configuration
print_status ""
print_status "Step 2: Updating database configuration..."
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/set_config.py
check_status "Database configuration update"
# Step 3: Check and fix file permissions
print_status ""
print_status "Step 3: Checking file permissions..."
sudo chmod +x /var/www/nebuleair_pro_4g/update_firmware.sh
sudo chmod 755 /var/www/nebuleair_pro_4g/sqlite/*.py
sudo chmod 755 /var/www/nebuleair_pro_4g/NPM/*.py
sudo chmod 755 /var/www/nebuleair_pro_4g/BME280/*.py
sudo chmod 755 /var/www/nebuleair_pro_4g/SARA/*.py
sudo chmod 755 /var/www/nebuleair_pro_4g/envea/*.py
check_status "File permissions update"
# Step 4: Restart critical services if they exist
print_status ""
print_status "Step 4: Managing system services..."
# List of services to check and restart
services=(
"nebuleair-npm-data.timer"
"nebuleair-envea-data.timer"
"nebuleair-sara-data.timer"
"nebuleair-bme280-data.timer"
"nebuleair-mppt-data.timer"
)
for service in "${services[@]}"; do
if systemctl list-unit-files | grep -q "$service"; then
print_status "Restarting service: $service"
sudo systemctl restart "$service"
if systemctl is-active --quiet "$service"; then
print_status "$service is running"
else
print_status "$service may not be active"
fi
else
print_status " Service $service not found (may not be installed)"
fi
done
# Step 5: System health check
print_status ""
print_status "Step 5: System health check..."
# Check disk space
disk_usage=$(df / | awk 'NR==2 {print $5}' | sed 's/%//')
if [ "$disk_usage" -gt 90 ]; then
print_status "⚠ Warning: Disk usage is high ($disk_usage%)"
else
print_status "✓ Disk usage is acceptable ($disk_usage%)"
fi
# Check if database is accessible
if [ -f "/var/www/nebuleair_pro_4g/sqlite/sensors.db" ]; then
print_status "✓ Database file exists"
else
print_status "⚠ Warning: Database file not found"
fi
# Step 6: Final cleanup
print_status ""
print_status "Step 6: Cleaning up..."
sudo find /var/www/nebuleair_pro_4g/logs -name "*.log" -size +10M -exec truncate -s 0 {} \;
check_status "Log cleanup"
print_status "Update completed successfully!"
exit 0

View File

@@ -18,6 +18,38 @@ Attention: The Raspberry Pi doesn't have analog inputs, so we need an analog-to
sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read.py sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read.py
this need to run as a service
--> sudo nano /etc/systemd/system/windMeter.service
⬇️
[Unit]
Description=Master manager for the Python wind meter scripts
After=network.target
[Service]
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/windMeter/read.py
Restart=always
User=root
WorkingDirectory=/var/www/nebuleair_pro_4g
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/wind.log
StandardError=append:/var/www/nebuleair_pro_4g/logs/wind_errors.log
[Install]
WantedBy=multi-user.target
⬆️
Reload systemd (first time after creating the service):
sudo systemctl daemon-reload
Enable (once), start (once and after stopping) and restart (after modification)systemd:
sudo systemctl enable windMeter.service
sudo systemctl start windMeter.service
sudo systemctl restart windMeter.service
Check the service status:
sudo systemctl status windMeter.service
''' '''
#!/usr/bin/python3 #!/usr/bin/python3
import time import time