Compare commits
51 Commits
4bc05091be
...
ai_branch_
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
020594e065 | ||
|
|
5a1a4e0d81 | ||
|
|
3cd5b13c25 | ||
|
|
5a0f1c0745 | ||
|
|
2516a3bd1c | ||
|
|
1b8dc54fe0 | ||
|
|
2bd74ca91a | ||
|
|
f40c105abf | ||
|
|
fdef8e2df0 | ||
|
|
386ad6fb03 | ||
|
|
a7c138e93f | ||
|
|
4e4832b128 | ||
|
|
11463b175c | ||
|
|
c06741b11d | ||
|
|
b1352261e7 | ||
|
|
376ff454bf | ||
|
|
932fdf83a2 | ||
|
|
1ca3e2ada2 | ||
|
|
fd1d32a62b | ||
|
|
61b302fe35 | ||
|
|
2aaa229e82 | ||
|
|
fd28069b0c | ||
|
|
b17c996f2f | ||
|
|
8273307cab | ||
|
|
a73eb30d32 | ||
|
|
ba889feee9 | ||
|
|
12c7a0b6af | ||
|
|
08c5ed8841 | ||
|
|
7f5eb7608c | ||
|
|
44f44c3361 | ||
|
|
a8350332ac | ||
|
|
6c6eed1ad6 | ||
|
|
ee71c28d33 | ||
|
|
6d3220665e | ||
|
|
98e5a239f5 | ||
|
|
17f4ce46dd | ||
|
|
338b8a049f | ||
|
|
1e9e80ae55 | ||
|
|
9d280c6e37 | ||
|
|
d4c1178b3d | ||
|
|
f7f6fccd60 | ||
|
|
afceb34c1b | ||
|
|
7a958d5c8e | ||
|
|
8fd76001f2 | ||
|
|
e320a3bc2b | ||
|
|
8a4e184699 | ||
|
|
e61b0a76da | ||
|
|
970a36598c | ||
|
|
e75caff929 | ||
|
|
e82d75a4d6 | ||
|
|
dc27e5f139 |
@@ -12,9 +12,9 @@ GPIO 20 -> SARA PWR ON
|
||||
|
||||
option 1:
|
||||
CLI tool like pinctrl
|
||||
pinctrl set 17 op
|
||||
pinctrl set 17 dh
|
||||
pinctrl set 17 dl
|
||||
pinctrl set 16 op
|
||||
pinctrl set 16 dh
|
||||
pinctrl set 16 dl
|
||||
|
||||
option 2:
|
||||
python library RPI.GPIO
|
||||
|
||||
@@ -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 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 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/
|
||||
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
|
||||
sudo crontab /var/www/nebuleair_pro_4g/cron_jobs
|
||||
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/ssh
|
||||
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
|
||||
|
||||
|
||||
@@ -25,23 +25,8 @@ url = parameter[1] # ex: data.mobileair.fr
|
||||
|
||||
profile_id = 3
|
||||
|
||||
#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)
|
||||
send_uSpot = config.get('send_uSpot', False)
|
||||
baudrate = 115200
|
||||
send_uSpot = False
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_line=None):
|
||||
response = bytearray()
|
||||
|
||||
@@ -26,23 +26,8 @@ url = parameter[1] # ex: data.mobileair.fr
|
||||
|
||||
profile_id = 3
|
||||
|
||||
#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)
|
||||
send_uSpot = config.get('send_uSpot', False)
|
||||
baudrate = 115200
|
||||
send_uSpot = False
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||
response = bytearray()
|
||||
|
||||
@@ -28,23 +28,8 @@ url = parameter[1] # ex: data.mobileair.fr
|
||||
endpoint = parameter[2]
|
||||
profile_id = 2
|
||||
|
||||
#get baudrate
|
||||
def load_config(config_file):
|
||||
try:
|
||||
with open(config_file, 'r') as file:
|
||||
config_data = json.load(file)
|
||||
return config_data
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
return {}
|
||||
|
||||
# Define the config file path
|
||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||
# Load the configuration data
|
||||
config = load_config(config_file)
|
||||
# Access the shared variables
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
send_uSpot = config.get('send_uSpot', False)
|
||||
baudrate = 115200
|
||||
send_uSpot = False
|
||||
|
||||
def color_text(text, color):
|
||||
colors = {
|
||||
|
||||
@@ -31,23 +31,8 @@ endpoint = parameter[2]
|
||||
|
||||
profile_id = 3
|
||||
|
||||
#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)
|
||||
send_uSpot = config.get('send_uSpot', False)
|
||||
baudrate = 115200
|
||||
send_uSpot = False
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||
response = bytearray()
|
||||
|
||||
@@ -21,23 +21,8 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
url = parameter[1] # ex: data.mobileair.fr
|
||||
|
||||
|
||||
#get baudrate
|
||||
def load_config(config_file):
|
||||
try:
|
||||
with open(config_file, 'r') as file:
|
||||
config_data = json.load(file)
|
||||
return config_data
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
return {}
|
||||
|
||||
# Define the config file path
|
||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||
# Load the configuration data
|
||||
config = load_config(config_file)
|
||||
# Access the shared variables
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
send_uSpot = config.get('send_uSpot', False)
|
||||
baudrate = 115200
|
||||
send_uSpot = False
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||
response = bytearray()
|
||||
|
||||
@@ -23,24 +23,8 @@ parameter = sys.argv[1:] # Exclude the script name
|
||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
url = parameter[1] # ex: data.mobileair.fr
|
||||
|
||||
|
||||
#get baudrate
|
||||
def load_config(config_file):
|
||||
try:
|
||||
with open(config_file, 'r') as file:
|
||||
config_data = json.load(file)
|
||||
return config_data
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
return {}
|
||||
|
||||
# Define the config file path
|
||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||
# Load the configuration data
|
||||
config = load_config(config_file)
|
||||
# Access the shared variables
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
send_uSpot = config.get('send_uSpot', False)
|
||||
baudrate = 115200
|
||||
send_uSpot = False
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||
response = bytearray()
|
||||
|
||||
@@ -14,19 +14,7 @@ parameter = sys.argv[1:] # Exclude the script name
|
||||
port = '/dev/' + parameter[0] # e.g., ttyAMA2
|
||||
timeout = float(parameter[1]) # e.g., 2 seconds
|
||||
|
||||
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'
|
||||
config = load_config(config_file)
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
baudrate = 115200
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2):
|
||||
response = bytearray()
|
||||
|
||||
@@ -13,12 +13,20 @@ Script that starts at the boot of the RPI (with cron)
|
||||
|
||||
'''
|
||||
import serial
|
||||
import RPi.GPIO as GPIO
|
||||
import time
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
import sqlite3
|
||||
|
||||
#GPIO
|
||||
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()
|
||||
@@ -56,36 +64,6 @@ def load_config_sqlite():
|
||||
print(f"Error loading config from SQLite: {e}")
|
||||
return {}
|
||||
|
||||
#Fonction pour mettre à jour le JSON de configuration
|
||||
def update_json_key(file_path, key, value):
|
||||
"""
|
||||
Updates a specific key in a JSON file with a new value.
|
||||
|
||||
:param file_path: Path to the JSON file.
|
||||
:param key: The key to update in the JSON file.
|
||||
:param value: The new value to assign to the key.
|
||||
"""
|
||||
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
|
||||
if key in data:
|
||||
data[key] = value # Update the key with the new value
|
||||
else:
|
||||
print(f"Key '{key}' not found in the JSON file.")
|
||||
return
|
||||
|
||||
# Write the updated data back to the file
|
||||
with open(file_path, "w") as file:
|
||||
json.dump(data, file, indent=2) # Use indent for pretty printing
|
||||
|
||||
print(f"💾 updating '{key}' to '{value}'.")
|
||||
except Exception as e:
|
||||
print(f"Error updating the JSON file: {e}")
|
||||
|
||||
|
||||
def update_sqlite_config(key, value):
|
||||
"""
|
||||
Updates a specific key in the SQLite config_table with a new value.
|
||||
@@ -195,6 +173,10 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
||||
try:
|
||||
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
|
||||
#Attention:
|
||||
# SARA R4 response: Manufacturer: u-blox Model: SARA-R410M-02B
|
||||
@@ -319,7 +301,7 @@ try:
|
||||
print(response_SARA_5cf)
|
||||
time.sleep(0.5)
|
||||
|
||||
#step 4: set url (op_code = 1)
|
||||
#step 4: set url (op_code = 1)
|
||||
print("➡️SET URL")
|
||||
command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
|
||||
17
SARA/sara.py
17
SARA/sara.py
@@ -32,23 +32,8 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
command = parameter[1] # ex: AT+CCID?
|
||||
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
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
|
||||
baudrate = 115200
|
||||
|
||||
try:
|
||||
|
||||
|
||||
@@ -22,23 +22,7 @@ parameter = sys.argv[1:] # Exclude the script name
|
||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
url = parameter[1] # ex: data.mobileair.fr
|
||||
|
||||
|
||||
#get baudrate
|
||||
def load_config(config_file):
|
||||
try:
|
||||
with open(config_file, 'r') as file:
|
||||
config_data = json.load(file)
|
||||
return config_data
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
return {}
|
||||
|
||||
# Define the config file path
|
||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||
# Load the configuration data
|
||||
config = load_config(config_file)
|
||||
# Access the shared variables
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -26,15 +26,7 @@ networkID = parameter[1] # ex: 20801
|
||||
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 {}
|
||||
|
||||
|
||||
def read_complete_response(serial_connection, timeout=2, end_of_response_timeout=2, wait_for_lines=None, debug=True):
|
||||
'''
|
||||
@@ -81,12 +73,7 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
||||
return response.decode('utf-8', errors='replace') # Return the full response if no target line is found
|
||||
|
||||
|
||||
# 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)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -11,22 +11,7 @@ parameter = sys.argv[1:] # Exclude the script name
|
||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
message = parameter[1] # ex: Hello
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -18,24 +18,7 @@ import sys
|
||||
import json
|
||||
|
||||
|
||||
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port='/dev/ttyAMA2',
|
||||
|
||||
@@ -17,23 +17,7 @@ import json
|
||||
# SARA R4 UHTTPC profile IDs
|
||||
aircarto_profile_id = 0
|
||||
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser_sara = serial.Serial(
|
||||
port='/dev/ttyAMA2',
|
||||
|
||||
@@ -11,22 +11,7 @@ parameter = sys.argv[1:] # Exclude the script name
|
||||
port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
message = parameter[1] # ex: Hello
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -12,22 +12,7 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
endpoint = parameter[1] # ex: /pro_4G/notif_message.php
|
||||
profile_id = parameter[2]
|
||||
|
||||
#get baudrate
|
||||
def load_config(config_file):
|
||||
try:
|
||||
with open(config_file, 'r') as file:
|
||||
config_data = json.load(file)
|
||||
return config_data
|
||||
except Exception as e:
|
||||
print(f"Error loading config file: {e}")
|
||||
return {}
|
||||
|
||||
# Define the config file path
|
||||
config_file = '/var/www/nebuleair_pro_4g/config.json'
|
||||
# Load the configuration data
|
||||
config = load_config(config_file)
|
||||
# Access the shared variables
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -21,23 +21,7 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
apn_address = parameter[1] # ex: data.mono
|
||||
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
|
||||
baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -27,22 +27,7 @@ url = parameter[1] # ex: data.mobileair.fr
|
||||
profile_id = parameter[2] #ex: 0
|
||||
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -40,22 +40,7 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
||||
|
||||
return response.decode('utf-8', errors='replace')
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser_sara = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -12,21 +12,7 @@ port='/dev/'+parameter[0] # ex: ttyAMA2
|
||||
message = parameter[1] # ex: Hello
|
||||
|
||||
#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)
|
||||
baudrate = 115200
|
||||
|
||||
ser = serial.Serial(
|
||||
port=port, #USB0 or ttyS0
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
# @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"
|
||||
JSON_FILE="/var/www/nebuleair_pro_4g/config.json"
|
||||
|
||||
|
||||
echo "-------------------"
|
||||
@@ -70,8 +69,6 @@ else
|
||||
# Update SQLite to reflect hotspot mode
|
||||
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
|
||||
#echo "Démarrage du tunnel SSH sur le port $SSH_TUNNEL_PORT..."
|
||||
# Start the SSH agent if it's not already running
|
||||
|
||||
@@ -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
|
||||
|
||||
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/app.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/app.log
|
||||
|
||||
0 0 * * * find /var/www/nebuleair_pro_4g/logs -name "*.log" -type f -exec truncate -s 0 {} \;
|
||||
|
||||
|
||||
|
||||
577
html/admin.html
577
html/admin.html
@@ -69,47 +69,52 @@
|
||||
<input type="text" class="form-control" id="device_ID" disabled>
|
||||
</div>
|
||||
|
||||
<!-- config_scripts_table -->
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_NPM" onchange="update_config_scripts_sqlite('NPM/get_data_modbus_v3.py', this.checked)">
|
||||
<label class="form-check-label" for="check_NPM">
|
||||
Next PM
|
||||
</label>
|
||||
<div class="mb-3">
|
||||
<label for="modem_version" class="form-label">Modem Version</label>
|
||||
<input type="text" class="form-control" id="modem_version" disabled>
|
||||
</div>
|
||||
|
||||
<!-- config_scripts_table -->
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_NPM_5channels" onchange="update_config_sqlite('npm_5channel', this.checked)">
|
||||
<label class="form-check-label" for="check_NPM_5channels">
|
||||
Next PM send 5 channels
|
||||
Send Next PM 5 channels data
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_bme280" onchange="update_config_scripts_sqlite('BME280/get_data_v2.py', this.checked)">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_bme280" onchange="update_config_sqlite('BME280', this.checked)">
|
||||
<label class="form-check-label" for="check_bme280">
|
||||
Sonde temp/hum (BME280)
|
||||
Send temp/hum data (BME280)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_envea" onchange="update_config_scripts_sqlite('envea/read_value_v2.py', this.checked)">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_envea" onchange="update_config_sqlite('envea', this.checked)">
|
||||
<label class="form-check-label" for="check_envea">
|
||||
Sonde Envea
|
||||
Send Envea sensor data
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_solarBattery" onchange="update_config_scripts_sqlite('MPPT/read.py', this.checked)">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_solarBattery" onchange="update_config_sqlite('MPPT', this.checked)">
|
||||
<label class="form-check-label" for="check_solarBattery">
|
||||
Solar / Battery MPPT
|
||||
Send Solar / Battery MPPT data
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_WindMeter" onchange="update_config_scripts_sqlite('windMeter/read.py', this.checked)">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_WindMeter" onchange="update_config_sqlite('windMeter', this.checked)">
|
||||
<label class="form-check-label" for="check_WindMeter">
|
||||
Wind Meter
|
||||
Send Wind Meter data
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_uSpot" onchange="update_config_sqlite('send_uSpot', this.checked)" disabled>
|
||||
<label class="form-check-label" for="check_uSpot">
|
||||
Send to uSpot
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -160,12 +165,77 @@
|
||||
<div class="col-lg-4 col-12">
|
||||
<h3 class="mt-4">Updates</h3>
|
||||
|
||||
<button type="submit" class="btn btn-primary" onclick="updateGitPull()">Update firmware</button>
|
||||
<button type="submit" class="btn btn-primary" onclick="updateFirmware()" id="updateBtn">
|
||||
<span id="updateBtnText">Update firmware</span>
|
||||
<span id="updateSpinner" class="spinner-border spinner-border-sm ms-2" style="display: none;"></span>
|
||||
</button>
|
||||
|
||||
<!-- Update Output Console -->
|
||||
<div id="updateOutput" class="mt-3" style="display: none;">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="fw-bold">Update Log</span>
|
||||
<div>
|
||||
<button type="button" class="btn btn-sm btn-success me-2" onclick="location.reload()" id="reloadBtn" style="display: none;">
|
||||
<svg width="14" height="14" fill="currentColor" class="bi bi-arrow-clockwise me-1" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||
</svg>
|
||||
Reload Page
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearUpdateOutput()">
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre id="updateOutputContent" class="mb-0" style="max-height: 400px; overflow-y: auto; font-size: 0.85rem; background-color: #f8f9fa; padding: 1rem; border-radius: 0.375rem;"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- SYSTEMD SERVICES SECTION -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-lg-8 col-12">
|
||||
<h4 class="mt-4">SystemD Services</h4>
|
||||
<div id="services-table" class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span class="fw-bold">Service Status</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshServices()">
|
||||
<svg width="14" height="14" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||
</svg>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<table class="table table-sm table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th style="width: 20%">Service</th>
|
||||
<th style="width: 25%">Description</th>
|
||||
<th style="width: 15%">Frequency</th>
|
||||
<th style="width: 10%">Status</th>
|
||||
<th style="width: 10%">Enabled</th>
|
||||
<th style="width: 20%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="services-tbody">
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-3">Loading services...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- toast -->
|
||||
|
||||
@@ -244,53 +314,41 @@ window.onload = function() {
|
||||
elements.forEach((element) => {
|
||||
element.innerText = response.deviceName;
|
||||
});
|
||||
//device name html page title
|
||||
if (response.deviceName) {
|
||||
document.title = response.deviceName;
|
||||
}
|
||||
//device ID
|
||||
const deviceID = response.deviceID.trim().toUpperCase();
|
||||
const device_ID = document.getElementById("device_ID");
|
||||
device_ID.value = response.deviceID.toUpperCase();
|
||||
//nextPM send 5 channels
|
||||
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
|
||||
checkbox_nmp5channels.checked = response.npm_5channel;
|
||||
//modem_version
|
||||
const modem_version = document.getElementById("modem_version");
|
||||
modem_version.value = response.modem_version;
|
||||
|
||||
|
||||
|
||||
const checkbox_nmp5channels = document.getElementById("check_NPM_5channels");
|
||||
const checkbox_wind = document.getElementById("check_WindMeter");
|
||||
const checkbox_uSpot = document.getElementById("check_uSpot");
|
||||
const checkbox_bme = document.getElementById("check_bme280");
|
||||
const checkbox_envea = document.getElementById("check_envea");
|
||||
const checkbox_solar = document.getElementById("check_solarBattery");
|
||||
|
||||
checkbox_bme.checked = response["BME280"];
|
||||
checkbox_envea.checked = response["envea"];
|
||||
checkbox_solar.checked = response["MPPT"];
|
||||
checkbox_nmp5channels.checked = response.npm_5channel;
|
||||
checkbox_wind.checked = response["windMeter"];
|
||||
checkbox_uSpot.checked = response["send_uSpot"];
|
||||
|
||||
},
|
||||
error: function(xhr, 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 checkbox_NPM = document.getElementById("check_NPM");
|
||||
const checkbox_bme = document.getElementById("check_bme280");
|
||||
const checkbox_envea = document.getElementById("check_envea");
|
||||
const checkbox_solar = document.getElementById("check_solarBattery");
|
||||
const checkbox_wind = document.getElementById("check_WindMeter");
|
||||
|
||||
checkbox_NPM.checked = response["NPM/get_data_modbus_v3.py"];
|
||||
checkbox_bme.checked = response["BME280/get_data_v2.py"];
|
||||
checkbox_envea.checked = response["envea/read_value_v2.py"];
|
||||
checkbox_solar.checked = response["MPPT/read.py"];
|
||||
checkbox_wind.checked = response["windMeter/read.py"];
|
||||
|
||||
//si sonde envea is true
|
||||
if (response["envea/read_value_v2.py"]) {
|
||||
add_sondeEnveaContainer();
|
||||
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
}
|
||||
});//end AJAX
|
||||
|
||||
|
||||
|
||||
//OLD way to get config (JSON)
|
||||
@@ -385,6 +443,8 @@ window.onload = function() {
|
||||
}
|
||||
}); //end AJAx
|
||||
|
||||
// Load services on page load
|
||||
refreshServices();
|
||||
|
||||
} //end window.onload
|
||||
|
||||
@@ -442,62 +502,6 @@ function update_config_sqlite(param, value){
|
||||
});
|
||||
}
|
||||
|
||||
function update_config_scripts_sqlite(param, value) {
|
||||
console.log("Updating scripts sqlite ", param, " : ", value);
|
||||
const toastLiveExample = document.getElementById('liveToast')
|
||||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=update_config_scripts_sqlite¶m=' + param + '&value=' + value,
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
success: function(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.script_path || param}<br>
|
||||
Value: ${response.enabled !== undefined ? response.enabled : value}<br>
|
||||
${response.message || ''}
|
||||
`;
|
||||
|
||||
if (response.script_path == "envea/read_value_v2.py") {
|
||||
console.log("envea sondes activated");
|
||||
add_sondeEnveaContainer();
|
||||
|
||||
}
|
||||
} 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.script_path || 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) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function update_config(param, value){
|
||||
console.log("Updating ",param," : ", value);
|
||||
@@ -515,28 +519,112 @@ function update_config(param, value){
|
||||
});
|
||||
}
|
||||
|
||||
function updateGitPull(){
|
||||
console.log("Updating device (git pull)");
|
||||
|
||||
function updateFirmware() {
|
||||
console.log("Starting comprehensive firmware update...");
|
||||
|
||||
// Show loading state
|
||||
const updateBtn = document.getElementById('updateBtn');
|
||||
const updateBtnText = document.getElementById('updateBtnText');
|
||||
const updateSpinner = document.getElementById('updateSpinner');
|
||||
const updateOutput = document.getElementById('updateOutput');
|
||||
const updateOutputContent = document.getElementById('updateOutputContent');
|
||||
|
||||
// Disable button and show spinner
|
||||
updateBtn.disabled = true;
|
||||
updateBtnText.textContent = 'Updating...';
|
||||
updateSpinner.style.display = 'inline-block';
|
||||
|
||||
// Show output console
|
||||
updateOutput.style.display = 'block';
|
||||
updateOutputContent.textContent = 'Starting update process...\n';
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=git_pull',
|
||||
method: 'GET', // Use GET or POST depending on your needs
|
||||
dataType: 'text', // Specify that you expect a JSON response
|
||||
|
||||
url: 'launcher.php?type=update_firmware',
|
||||
method: 'GET',
|
||||
dataType: 'json',
|
||||
timeout: 120000, // 2 minutes timeout
|
||||
|
||||
success: function(response) {
|
||||
// Handle success response if needed
|
||||
console.log(response);
|
||||
alert(response);
|
||||
// Reload the page after the device update
|
||||
location.reload(); // This will reload the page
|
||||
|
||||
console.log('Update completed:', response);
|
||||
|
||||
// Display formatted output
|
||||
if (response.success && response.output) {
|
||||
// Format the output for better readability
|
||||
const formattedOutput = response.output
|
||||
.replace(/\[\d{2}:\d{2}:\d{2}\]/g, function(match) {
|
||||
return `<span style="color: #007bff; font-weight: bold;">${match}</span>`;
|
||||
})
|
||||
.replace(/✓/g, '<span style="color: #28a745;">✓</span>')
|
||||
.replace(/✗/g, '<span style="color: #dc3545;">✗</span>')
|
||||
.replace(/⚠/g, '<span style="color: #ffc107;">⚠</span>')
|
||||
.replace(/ℹ/g, '<span style="color: #17a2b8;">ℹ</span>');
|
||||
|
||||
updateOutputContent.innerHTML = formattedOutput;
|
||||
|
||||
// Show success toast and reload button
|
||||
showToast('Update completed successfully!', 'success');
|
||||
document.getElementById('reloadBtn').style.display = 'inline-block';
|
||||
} else {
|
||||
updateOutputContent.textContent = 'Update completed but no output received.';
|
||||
showToast('Update may have completed with issues', 'warning');
|
||||
}
|
||||
},
|
||||
|
||||
error: function(xhr, status, error) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
console.error('Update failed:', status, error);
|
||||
updateOutputContent.textContent = `Update failed: ${error}\n\nStatus: ${status}\nResponse: ${xhr.responseText || 'No response'}`;
|
||||
showToast('Update failed! Check the output for details.', 'error');
|
||||
},
|
||||
|
||||
complete: function() {
|
||||
// Reset button state
|
||||
updateBtn.disabled = false;
|
||||
updateBtnText.textContent = 'Update firmware';
|
||||
updateSpinner.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function clearUpdateOutput() {
|
||||
const updateOutput = document.getElementById('updateOutput');
|
||||
const updateOutputContent = document.getElementById('updateOutputContent');
|
||||
const reloadBtn = document.getElementById('reloadBtn');
|
||||
|
||||
updateOutputContent.textContent = '';
|
||||
updateOutput.style.display = 'none';
|
||||
reloadBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
function showToast(message, type) {
|
||||
const toastLiveExample = document.getElementById('liveToast');
|
||||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||
|
||||
// Set toast color based on type
|
||||
toastLiveExample.classList.remove('text-bg-primary', 'text-bg-success', 'text-bg-danger', 'text-bg-warning');
|
||||
switch(type) {
|
||||
case 'success':
|
||||
toastLiveExample.classList.add('text-bg-success');
|
||||
break;
|
||||
case 'error':
|
||||
toastLiveExample.classList.add('text-bg-danger');
|
||||
break;
|
||||
case 'warning':
|
||||
toastLiveExample.classList.add('text-bg-warning');
|
||||
break;
|
||||
default:
|
||||
toastLiveExample.classList.add('text-bg-primary');
|
||||
}
|
||||
|
||||
toastBody.textContent = message;
|
||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||
toastBootstrap.show();
|
||||
}
|
||||
|
||||
// Legacy function for backward compatibility
|
||||
function updateGitPull() {
|
||||
updateFirmware();
|
||||
}
|
||||
|
||||
function set_RTC_withNTP(){
|
||||
console.log("Set RTC module with WIFI (NTP server)");
|
||||
|
||||
@@ -901,6 +989,251 @@ function updateSondeCoefficient(id, coefficient) {
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
____ _ __ __ _
|
||||
/ ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_
|
||||
\___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __|
|
||||
___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_
|
||||
|____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__|
|
||||
|___/
|
||||
*/
|
||||
|
||||
function refreshServices() {
|
||||
console.log("Refreshing services status");
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=get_systemd_services',
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
success: function(response) {
|
||||
console.log("Services data:", response);
|
||||
|
||||
if (response.success) {
|
||||
displayServices(response.services);
|
||||
} else {
|
||||
showServiceError("Failed to load services: " + response.error);
|
||||
}
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Failed to load services:', error);
|
||||
showServiceError("Failed to load services: " + error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayServices(services) {
|
||||
const tbody = document.getElementById('services-tbody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
services.forEach(function(service) {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Service name
|
||||
const nameCell = document.createElement('td');
|
||||
nameCell.textContent = service.display_name || service.name;
|
||||
row.appendChild(nameCell);
|
||||
|
||||
// Description
|
||||
const descCell = document.createElement('td');
|
||||
descCell.textContent = service.description || 'No description available';
|
||||
descCell.className = 'text-muted small';
|
||||
row.appendChild(descCell);
|
||||
|
||||
// Frequency
|
||||
const freqCell = document.createElement('td');
|
||||
freqCell.textContent = service.frequency || 'Unknown';
|
||||
freqCell.className = 'text-primary small fw-bold';
|
||||
row.appendChild(freqCell);
|
||||
|
||||
// Status
|
||||
const statusCell = document.createElement('td');
|
||||
const statusBadge = document.createElement('span');
|
||||
statusBadge.className = `badge ${service.active ? 'bg-success' : 'bg-danger'}`;
|
||||
statusBadge.textContent = service.active ? 'Running' : 'Stopped';
|
||||
statusCell.appendChild(statusBadge);
|
||||
row.appendChild(statusCell);
|
||||
|
||||
// Enabled
|
||||
const enabledCell = document.createElement('td');
|
||||
const enabledBadge = document.createElement('span');
|
||||
enabledBadge.className = `badge ${service.enabled ? 'bg-info' : 'bg-secondary'}`;
|
||||
enabledBadge.textContent = service.enabled ? 'Enabled' : 'Disabled';
|
||||
enabledCell.appendChild(enabledBadge);
|
||||
row.appendChild(enabledCell);
|
||||
|
||||
// Actions
|
||||
const actionsCell = document.createElement('td');
|
||||
|
||||
// Restart button
|
||||
const restartBtn = document.createElement('button');
|
||||
restartBtn.className = 'btn btn-sm btn-warning me-2';
|
||||
restartBtn.innerHTML = '<i class="bi bi-arrow-clockwise"></i> Restart';
|
||||
restartBtn.onclick = function() {
|
||||
restartService(service.name);
|
||||
};
|
||||
actionsCell.appendChild(restartBtn);
|
||||
|
||||
// Enable/Disable button
|
||||
const toggleBtn = document.createElement('button');
|
||||
toggleBtn.className = `btn btn-sm ${service.enabled ? 'btn-danger' : 'btn-success'}`;
|
||||
toggleBtn.innerHTML = service.enabled ? '<i class="bi bi-stop"></i> Disable' : '<i class="bi bi-play"></i> Enable';
|
||||
toggleBtn.onclick = function() {
|
||||
toggleService(service.name, !service.enabled);
|
||||
};
|
||||
actionsCell.appendChild(toggleBtn);
|
||||
|
||||
row.appendChild(actionsCell);
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function showServiceError(message) {
|
||||
const tbody = document.getElementById('services-tbody');
|
||||
tbody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-danger">
|
||||
<i class="bi bi-exclamation-triangle"></i> ${message}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
|
||||
function restartService(serviceName) {
|
||||
console.log(`Restarting service: ${serviceName}`);
|
||||
|
||||
if (!confirm(`Are you sure you want to restart ${serviceName}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toastLiveExample = document.getElementById('liveToast');
|
||||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=restart_systemd_service&service=' + encodeURIComponent(serviceName),
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
success: function(response) {
|
||||
console.log('Service restart response:', response);
|
||||
|
||||
let formattedMessage = '';
|
||||
|
||||
if (response.success) {
|
||||
// Success message
|
||||
toastLiveExample.classList.remove('text-bg-danger');
|
||||
toastLiveExample.classList.add('text-bg-success');
|
||||
|
||||
formattedMessage = `
|
||||
<strong>Success!</strong><br>
|
||||
Service: ${serviceName}<br>
|
||||
${response.message || 'Service restarted successfully'}
|
||||
`;
|
||||
|
||||
// Refresh services after a short delay
|
||||
setTimeout(refreshServices, 2000);
|
||||
} else {
|
||||
// Error message
|
||||
toastLiveExample.classList.remove('text-bg-success');
|
||||
toastLiveExample.classList.add('text-bg-danger');
|
||||
|
||||
formattedMessage = `
|
||||
<strong>Error!</strong><br>
|
||||
Service: ${serviceName}<br>
|
||||
${response.error || 'Unknown error occurred'}
|
||||
`;
|
||||
}
|
||||
|
||||
// Update and show toast
|
||||
toastBody.innerHTML = formattedMessage;
|
||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||
toastBootstrap.show();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Failed to restart service:', error);
|
||||
|
||||
// Show error toast
|
||||
toastLiveExample.classList.remove('text-bg-success');
|
||||
toastLiveExample.classList.add('text-bg-danger');
|
||||
toastBody.innerHTML = `
|
||||
<strong>Request Failed!</strong><br>
|
||||
Service: ${serviceName}<br>
|
||||
Error: ${error}
|
||||
`;
|
||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||
toastBootstrap.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleService(serviceName, enable) {
|
||||
const action = enable ? 'enable' : 'disable';
|
||||
console.log(`${action} service: ${serviceName}`);
|
||||
|
||||
if (!confirm(`Are you sure you want to ${action} ${serviceName}?`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const toastLiveExample = document.getElementById('liveToast');
|
||||
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=toggle_systemd_service&service=' + encodeURIComponent(serviceName) + '&enable=' + enable,
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
cache: false,
|
||||
success: function(response) {
|
||||
console.log('Service toggle response:', response);
|
||||
|
||||
let formattedMessage = '';
|
||||
|
||||
if (response.success) {
|
||||
// Success message
|
||||
toastLiveExample.classList.remove('text-bg-danger');
|
||||
toastLiveExample.classList.add('text-bg-success');
|
||||
|
||||
formattedMessage = `
|
||||
<strong>Success!</strong><br>
|
||||
Service: ${serviceName}<br>
|
||||
${response.message || `Service ${action}d successfully`}
|
||||
`;
|
||||
|
||||
// Refresh services after a short delay
|
||||
setTimeout(refreshServices, 2000);
|
||||
} else {
|
||||
// Error message
|
||||
toastLiveExample.classList.remove('text-bg-success');
|
||||
toastLiveExample.classList.add('text-bg-danger');
|
||||
|
||||
formattedMessage = `
|
||||
<strong>Error!</strong><br>
|
||||
Service: ${serviceName}<br>
|
||||
${response.error || 'Unknown error occurred'}
|
||||
`;
|
||||
}
|
||||
|
||||
// Update and show toast
|
||||
toastBody.innerHTML = formattedMessage;
|
||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||
toastBootstrap.show();
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
console.error('Failed to toggle service:', error);
|
||||
|
||||
// Show error toast
|
||||
toastLiveExample.classList.remove('text-bg-success');
|
||||
toastLiveExample.classList.add('text-bg-danger');
|
||||
toastBody.innerHTML = `
|
||||
<strong>Request Failed!</strong><br>
|
||||
Service: ${serviceName}<br>
|
||||
Error: ${error}
|
||||
`;
|
||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||
toastBootstrap.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -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_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_WIND',getSelectedLimit(),false)">Sonde Vent</button>
|
||||
|
||||
<button class="btn btn-warning" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)">Timestamp Table</button>
|
||||
|
||||
</div>
|
||||
@@ -147,42 +149,55 @@
|
||||
|
||||
|
||||
|
||||
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;
|
||||
|
||||
const elements = document.querySelectorAll('.sideBar_sensorName');
|
||||
elements.forEach((element) => {
|
||||
element.innerText = deviceName;
|
||||
});
|
||||
window.onload = function() {
|
||||
|
||||
|
||||
//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);
|
||||
}
|
||||
//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;
|
||||
});
|
||||
|
||||
})
|
||||
.catch(error => console.error('Error loading config.json:', error));
|
||||
}
|
||||
//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);
|
||||
}
|
||||
}); //end AJAX
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -199,7 +214,6 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
||||
|
||||
console.log(url);
|
||||
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
dataType: 'text', // Specify that you expect a JSON response
|
||||
@@ -260,6 +274,12 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
||||
tableHTML += `
|
||||
<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>`;
|
||||
@@ -310,6 +330,12 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
||||
tableHTML += `
|
||||
<td>${columns[1]}</td>
|
||||
`;
|
||||
}else if (table === "data_WIND") {
|
||||
tableHTML += `
|
||||
<td>${columns[0]}</td>
|
||||
<td>${columns[1]}</td>
|
||||
<td>${columns[2]}</td>
|
||||
`;
|
||||
}
|
||||
|
||||
tableHTML += "</tr>";
|
||||
|
||||
@@ -151,12 +151,17 @@ window.onload = function() {
|
||||
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'
|
||||
|
||||
@@ -218,52 +218,6 @@ if ($type == "update_config_sqlite") {
|
||||
}
|
||||
}
|
||||
|
||||
//UPDATING the config_scripts table from SQLite DB
|
||||
if ($type == "update_config_scripts_sqlite") {
|
||||
$script_path = $_GET['param'] ?? null;
|
||||
$enabled = $_GET['value'] ?? null;
|
||||
|
||||
if ($script_path === null || $enabled === 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 enabled FROM config_scripts_table WHERE script_path = :script_path");
|
||||
$checkStmt->bindParam(':script_path', $script_path);
|
||||
$checkStmt->execute();
|
||||
$result = $checkStmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($result) {
|
||||
// Convert enabled value to 0 or 1
|
||||
$enabledValue = (filter_var($enabled, FILTER_VALIDATE_BOOLEAN)) ? 1 : 0;
|
||||
|
||||
// Update the enabled status
|
||||
$updateStmt = $db->prepare("UPDATE config_scripts_table SET enabled = :enabled WHERE script_path = :script_path");
|
||||
$updateStmt->bindParam(':enabled', $enabledValue, PDO::PARAM_INT);
|
||||
$updateStmt->bindParam(':script_path', $script_path);
|
||||
$updateStmt->execute();
|
||||
|
||||
echo json_encode([
|
||||
"success" => true,
|
||||
"message" => "Script configuration updated successfully",
|
||||
"script_path" => $script_path,
|
||||
"enabled" => (bool)$enabledValue
|
||||
], JSON_UNESCAPED_SLASHES); // Prevent escaping forward slashes
|
||||
} else {
|
||||
echo json_encode([
|
||||
"error" => "Script path not found in configuration",
|
||||
"script_path" => $script_path
|
||||
], JSON_UNESCAPED_SLASHES); // Prevent escaping forward slashes
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
echo json_encode(["error" => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
//UPDATING the envea_sondes_table table from SQLite DB
|
||||
if ($type == "update_sonde") {
|
||||
@@ -395,6 +349,20 @@ if ($type == "git_pull") {
|
||||
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") {
|
||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py';
|
||||
$output = shell_exec($command);
|
||||
@@ -423,9 +391,50 @@ if ($type == "set_RTC_withBrowser") {
|
||||
|
||||
|
||||
if ($type == "clear_loopLogs") {
|
||||
$command = 'truncate -s 0 /var/www/nebuleair_pro_4g/logs/loop.log';
|
||||
$output = shell_exec($command);
|
||||
echo $output;
|
||||
$response = array();
|
||||
|
||||
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") {
|
||||
@@ -590,36 +599,56 @@ if ($type == "sara_connectNetwork") {
|
||||
$port=$_GET['port'];
|
||||
$timeout=$_GET['timeout'];
|
||||
$networkID=$_GET['networkID'];
|
||||
$param="SARA_R4_neworkID";
|
||||
|
||||
//echo "updating SARA_R4_networkID in config file";
|
||||
|
||||
//OLD way to store data (JSON file)
|
||||
|
||||
// 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
|
||||
$configFile = '/var/www/nebuleair_pro_4g/config.json';
|
||||
//$configFile = '/var/www/nebuleair_pro_4g/config.json';
|
||||
// Read the JSON file
|
||||
$jsonData = file_get_contents($configFile);
|
||||
//$jsonData = file_get_contents($configFile);
|
||||
// Decode JSON data into an associative array
|
||||
$config = json_decode($jsonData, true);
|
||||
//$config = json_decode($jsonData, true);
|
||||
// Check if decoding was successful
|
||||
if ($config === null) {
|
||||
die("Error: Could not decode JSON file.");
|
||||
}
|
||||
//if ($config === null) {
|
||||
// die("Error: Could not decode JSON file.");
|
||||
//}
|
||||
// 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
|
||||
$newJsonData = json_encode($config, JSON_PRETTY_PRINT);
|
||||
//$newJsonData = json_encode($config, JSON_PRETTY_PRINT);
|
||||
// Check if encoding was successful
|
||||
if ($newJsonData === false) {
|
||||
die("Error: Could not encode JSON data.");
|
||||
}
|
||||
//if ($newJsonData === false) {
|
||||
// die("Error: Could not encode JSON data.");
|
||||
//}
|
||||
|
||||
// Write the updated JSON back to the file
|
||||
if (file_put_contents($configFile, $newJsonData) === false) {
|
||||
die("Error: Could not write to JSON file.");
|
||||
}
|
||||
//if (file_put_contents($configFile, $newJsonData) === false) {
|
||||
// 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.";
|
||||
|
||||
} 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...";
|
||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/SARA/sara_connectNetwork.py ' . $port . ' ' . $networkID . ' ' . $timeout;
|
||||
@@ -806,3 +835,366 @@ if ($type == "wifi_scan_old") {
|
||||
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()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
148
html/logs.html
148
html/logs.html
@@ -56,7 +56,10 @@
|
||||
<div class="col-lg-6 col-12">
|
||||
<div class="card" style="height: 80vh;">
|
||||
<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>
|
||||
</div>
|
||||
<div class="card-body overflow-auto" id="card_loop_content">
|
||||
@@ -69,6 +72,7 @@
|
||||
<div class="card" style="height: 80vh;">
|
||||
<div class="card-header">
|
||||
Boot logs
|
||||
<button type="submit" class="btn btn-secondary btn-sm" id="refresh-boot-log">Refresh</button>
|
||||
</div>
|
||||
<div class="card-body overflow-auto" id="card_boot_content">
|
||||
|
||||
@@ -111,65 +115,17 @@
|
||||
const boot_card_content = document.getElementById('card_boot_content');
|
||||
|
||||
//Getting Master logs
|
||||
console.log("Getting master logs");
|
||||
console.log("Getting SARA logs");
|
||||
displayLogFile('../logs/sara_service.log', loop_card_content, true, 1000);
|
||||
|
||||
console.log("Getting app/boot logs");
|
||||
displayLogFile('../logs/app.log', boot_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");
|
||||
|
||||
//Getting App logs
|
||||
fetch('../logs/app.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
|
||||
|
||||
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 master log with refresh button
|
||||
setupLogRefreshButton('refresh-master-log', '../logs/sara_service.log', 'card_loop_content', 3000);
|
||||
|
||||
// Setup boot log with refresh button
|
||||
setupLogRefreshButton('refresh-boot-log', '../logs/app.log', 'card_boot_content', 300);
|
||||
|
||||
|
||||
});
|
||||
@@ -195,6 +151,11 @@ window.onload = function() {
|
||||
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);
|
||||
@@ -220,6 +181,75 @@ 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();
|
||||
})
|
||||
.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(){
|
||||
console.log("Clearing loop logs");
|
||||
|
||||
118
html/saraR4.html
118
html/saraR4.html
@@ -372,6 +372,10 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
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) {
|
||||
@@ -387,9 +391,58 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
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){
|
||||
console.log("Data from SaraR4");
|
||||
console.log("Port: " + port );
|
||||
@@ -811,69 +864,6 @@ function update_modem_configMode(param, checked){
|
||||
|
||||
|
||||
|
||||
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>
|
||||
|
||||
|
||||
@@ -47,6 +47,12 @@
|
||||
</svg>
|
||||
Carte
|
||||
</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">
|
||||
<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"/>
|
||||
|
||||
413
html/terminal.html
Normal file
413
html/terminal.html
Normal 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>
|
||||
@@ -302,6 +302,11 @@ function get_internet(){
|
||||
element.innerText = deviceName;
|
||||
});
|
||||
|
||||
//device name html page title
|
||||
if (response.deviceName) {
|
||||
document.title = response.deviceName;
|
||||
}
|
||||
|
||||
|
||||
//get wifi connection status
|
||||
const WIFI_statusElement = document.getElementById("wifi-status");
|
||||
|
||||
@@ -29,34 +29,21 @@ sudo apt update && sudo apt install -y git gh apache2 sqlite3 php php-sqlite3 py
|
||||
info "Installing 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)
|
||||
REPO_DIR="/var/www/nebuleair_pro_4g"
|
||||
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
|
||||
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."
|
||||
@@ -66,7 +53,6 @@ fi
|
||||
info "Setting up repository files and permissions..."
|
||||
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 cp "$REPO_DIR/config.json.dist" "$REPO_DIR/config.json"
|
||||
sudo chmod -R 755 "$REPO_DIR/"
|
||||
sudo chown -R www-data:www-data "$REPO_DIR/"
|
||||
sudo git config --global core.fileMode false
|
||||
@@ -91,6 +77,15 @@ else
|
||||
warning "Database creation script not found."
|
||||
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
|
||||
info "Configuring Apache..."
|
||||
APACHE_CONF="/etc/apache2/sites-available/000-default.conf"
|
||||
@@ -105,7 +100,7 @@ fi
|
||||
# Add sudo authorization (prevent duplicate entries)
|
||||
info "Setting up sudo authorization..."
|
||||
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."
|
||||
else
|
||||
warning "Sudo authorization already set. Skipping."
|
||||
@@ -133,7 +128,6 @@ success "I2C ports enabled."
|
||||
info "Creates sqlites databases..."
|
||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/sqlite/create_db.py
|
||||
|
||||
|
||||
# Completion message
|
||||
success "Setup completed successfully!"
|
||||
info "System will reboot in 5 seconds..."
|
||||
|
||||
@@ -22,13 +22,19 @@ error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
error "This script must be run as root. Use 'sudo ./installation.sh'"
|
||||
fi
|
||||
|
||||
REPO_DIR="/var/www/nebuleair_pro_4g"
|
||||
#set up the RTC
|
||||
info "Set up the RTC"
|
||||
/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_NTP.py
|
||||
|
||||
#Check SARA R4 connection
|
||||
info "Check SARA R4 connection"
|
||||
#Wake up SARA
|
||||
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
|
||||
|
||||
#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
|
||||
|
||||
#Need to create the two service
|
||||
# 1. master_nebuleair
|
||||
# 1. start the scripts to set-up the services
|
||||
# 2. rtc_save_to_db
|
||||
|
||||
#1. Add master_nebuleair.service
|
||||
SERVICE_FILE="/etc/systemd/system/master_nebuleair.service"
|
||||
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
|
||||
|
||||
#1. set-up the services (SARA, NPM, BME280, etc)
|
||||
info "Setting up systemd services..."
|
||||
|
||||
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
|
||||
SERVICE_FILE_2="/etc/systemd/system/rtc_save_to_db.service"
|
||||
|
||||
@@ -54,6 +54,8 @@ CSV PAYLOAD (AirCarto Servers)
|
||||
22 -> solar_voltage
|
||||
23 -> solar_power
|
||||
24 -> charger_status
|
||||
25 -> Wind speed
|
||||
26 -> Wind direction
|
||||
|
||||
JSON PAYLOAD (Micro-Spot Servers)
|
||||
Same as NebuleAir wifi
|
||||
@@ -163,7 +165,7 @@ def blink_led(pin, blink_count, delay=1):
|
||||
|
||||
finally:
|
||||
GPIO.output(pin, GPIO.LOW) # Ensure LED is off
|
||||
print(f"LED on GPIO {pin} turned OFF (cleanup avoided)")
|
||||
#print(f"LED on GPIO {pin} turned OFF (cleanup avoided)")
|
||||
|
||||
#get config data from SQLite table
|
||||
def load_config_sqlite():
|
||||
@@ -198,30 +200,6 @@ def load_config_sqlite():
|
||||
print(f"Error loading config from SQLite: {e}")
|
||||
return {}
|
||||
|
||||
def load_config_scripts_sqlite():
|
||||
"""
|
||||
Load script configuration data from SQLite config_scripts_table
|
||||
|
||||
Returns:
|
||||
dict: Script paths as keys and enabled status as boolean values
|
||||
"""
|
||||
try:
|
||||
# Query the config_scripts_table
|
||||
cursor.execute("SELECT script_path, enabled FROM config_scripts_table")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
# Create config dictionary with script paths as keys and enabled status as boolean values
|
||||
scripts_config = {}
|
||||
for script_path, enabled in rows:
|
||||
# Convert integer enabled value (0/1) to boolean
|
||||
scripts_config[script_path] = bool(enabled)
|
||||
|
||||
return scripts_config
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error loading scripts config from SQLite: {e}")
|
||||
return {}
|
||||
|
||||
#Load config
|
||||
config = load_config_sqlite()
|
||||
#config
|
||||
@@ -232,17 +210,13 @@ device_latitude_raw = config.get('latitude_raw', 0)
|
||||
device_longitude_raw = config.get('longitude_raw', 0)
|
||||
modem_version=config.get('modem_version', "")
|
||||
Sara_baudrate = config.get('SaraR4_baudrate', 115200)
|
||||
npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM
|
||||
selected_networkID = int(config.get('SARA_R4_neworkID', 0))
|
||||
send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot ()
|
||||
reset_uSpot_url = False
|
||||
|
||||
#config_scripts
|
||||
config_scripts = load_config_scripts_sqlite()
|
||||
bme_280_config = config_scripts.get('BME280/get_data_v2.py', False)
|
||||
envea_cairsens= config_scripts.get('envea/read_value_v2.py', False)
|
||||
mppt_charger= config_scripts.get('MPPT/read.py', False)
|
||||
wind_meter= config_scripts.get('windMeter/read.py', False)
|
||||
npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM
|
||||
envea_cairsens= config.get('envea', False)
|
||||
wind_meter= config.get('windMeter', False)
|
||||
bme_280_config = config.get('BME280', False)
|
||||
mppt_charger= config.get('MPPT', False)
|
||||
|
||||
#update device id in the payload json
|
||||
payload_json["nebuleairid"] = device_id
|
||||
@@ -265,6 +239,9 @@ def read_complete_response(serial_connection, timeout=2, end_of_response_timeout
|
||||
'''
|
||||
Fonction très importante !!!
|
||||
Reads the complete response from a serial connection and waits for specific lines.
|
||||
timeout -> temps d'attente de la réponse de la première ligne (assez rapide car le SARA répond direct avec la commande recue)
|
||||
end_of_response_timeout -> le temps d'inactivité entre deux lignes imprimées (plus long dans certain cas: le SARA mouline avant de finir vraiment)
|
||||
wait_for_lines -> si on rencontre la string la fonction s'arrete
|
||||
'''
|
||||
if wait_for_lines is None:
|
||||
wait_for_lines = [] # Default to an empty list if not provided
|
||||
@@ -366,6 +343,116 @@ def send_error_notification(device_id, error_type, additional_info=None):
|
||||
|
||||
return False
|
||||
|
||||
def modem_hardware_reboot():
|
||||
"""
|
||||
Performs a hardware reboot using transistors connected to pin 16 and 20:
|
||||
pin 16 set to SARA GND
|
||||
pin 20 set to SARA ON (not used)
|
||||
|
||||
LOW -> cut the current
|
||||
HIGH -> current flow
|
||||
|
||||
"""
|
||||
print('<span style="color: orange;font-weight: bold;">🔄 Hardware SARA reboot 🔄</span>')
|
||||
|
||||
SARA_power_GPIO = 16
|
||||
SARA_ON_GPIO = 20
|
||||
|
||||
GPIO.setmode(GPIO.BCM) # Use BCM numbering
|
||||
GPIO.setup(SARA_power_GPIO, GPIO.OUT) # Set GPIO17 as an output
|
||||
|
||||
GPIO.output(SARA_power_GPIO, GPIO.LOW)
|
||||
time.sleep(2)
|
||||
GPIO.output(SARA_power_GPIO, GPIO.HIGH)
|
||||
time.sleep(2)
|
||||
|
||||
print("Checking if modem is responsive...")
|
||||
|
||||
for attempt in range(5):
|
||||
ser_sara.write(b'AT\r')
|
||||
response_check = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
||||
if response_check and "OK" in response_check:
|
||||
print("✅ Modem is responsive after reboot.")
|
||||
return True
|
||||
print(f"⏳ Waiting for modem... attempt {attempt + 1}")
|
||||
time.sleep(2)
|
||||
else:
|
||||
print("❌ Modem not responding after reboot.")
|
||||
return False
|
||||
|
||||
def reset_PSD_CSD_connection():
|
||||
"""
|
||||
Function that reset the PSD CSD connection for the SARA R5
|
||||
returns true or false
|
||||
"""
|
||||
print("⚠️Reseting PDP connection ")
|
||||
pdp_reset_success = True
|
||||
# Activate PDP context 1
|
||||
print('➡️ Activate PDP context 1')
|
||||
command = f'AT+CGACT=1,1\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp1 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_pdp1, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp1 is not None and "OK" in response_pdp1)
|
||||
time.sleep(1)
|
||||
|
||||
# Set the PDP type
|
||||
print('➡️ Set the PDP type to IPv4 referring to the output of the +CGDCONT read command')
|
||||
command = f'AT+UPSD=0,0,0\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_pdp2, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp2 is not None and "OK" in response_pdp2)
|
||||
time.sleep(1)
|
||||
|
||||
# Profile #0 is mapped on CID=1
|
||||
print('➡️ Profile #0 is mapped on CID=1.')
|
||||
command = f'AT+UPSD=0,100,1\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_pdp3, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp3 is not None and "OK" in response_pdp3)
|
||||
time.sleep(1)
|
||||
|
||||
# Activate the PSD profile
|
||||
print('➡️ Activate the PSD profile #0: the IPv4 address is already assigned by the network.')
|
||||
command = f'AT+UPSDA=0,3\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp4 = read_complete_response(ser_sara, wait_for_lines=["OK", "+UUPSDA"])
|
||||
print(response_pdp4, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp4 is not None and ("OK" in response_pdp4 or "+UUPSDA" in response_pdp4))
|
||||
time.sleep(1)
|
||||
|
||||
if not pdp_reset_success:
|
||||
print("⚠️ PDP connection reset had some issues")
|
||||
|
||||
return pdp_reset_success
|
||||
|
||||
def reset_server_hostname(profile_id):
|
||||
"""
|
||||
Function that reset server hostname (URL) connection for the SARA R5
|
||||
returns true or false
|
||||
"""
|
||||
print("⚠️Reseting Server Hostname connection ")
|
||||
http_reset_success = False # Default fallback
|
||||
|
||||
if profile_id == 0:
|
||||
print('<span style="color: orange;font-weight: bold;">🔧 Resetting AirCarto HTTP Profile</span>')
|
||||
command = f'AT+UHTTP={profile_id},1,"data.nebuleair.fr"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5)
|
||||
time.sleep(1)
|
||||
http_reset_success = response_SARA_5 is not None and "OK" in response_SARA_5
|
||||
if not http_reset_success:
|
||||
print("⚠️ AirCarto HTTP profile reset failed")
|
||||
elif profile_id ==1:
|
||||
pass # TODO: implement handling for profile 1
|
||||
else:
|
||||
print(f"❌ Unsupported profile ID: {profile_id}")
|
||||
http_reset_success = False
|
||||
return http_reset_success
|
||||
|
||||
def modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id):
|
||||
"""
|
||||
Performs a complete modem restart sequence:
|
||||
@@ -382,15 +469,17 @@ def modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id):
|
||||
bool: True if the complete sequence was successful, False otherwise
|
||||
"""
|
||||
print('<span style="color: orange;font-weight: bold;">🔄 Complete SARA reboot and reinitialize sequence 🔄</span>')
|
||||
|
||||
|
||||
# Step 1: Reboot the modem - Integrated modem_software_reboot logic
|
||||
print('<span style="color: orange;font-weight: bold;">🔄 Software SARA reboot! 🔄</span>')
|
||||
print('<span style="color: orange;font-weight: bold;">🔄 Software SARA reboot (CFUN)! 🔄</span>')
|
||||
|
||||
# Use different commands based on modem version
|
||||
if 'R5' in modem_version: # For SARA-R5 series
|
||||
command = 'AT+CFUN=16\r' # Normal restart for R5
|
||||
else: # For SARA-R4 series
|
||||
command = 'AT+CFUN=15\r' # Factory reset for R4
|
||||
|
||||
#ATTENTION : AT+CFUN=16 sometimes causes the modem to reset before replying OK
|
||||
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR"], debug=True)
|
||||
@@ -400,83 +489,154 @@ def modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id):
|
||||
print("</p>", end="")
|
||||
|
||||
# Check if reboot command was acknowledged
|
||||
reboot_success = response is not None and "OK" in response
|
||||
if not reboot_success:
|
||||
print("⚠️ Modem reboot command failed")
|
||||
return False
|
||||
if response is None or ("OK" not in response and "ERROR" in response):
|
||||
print("⚠️ Reboot command may have failed or modem restarted before responding.")
|
||||
# Still continue, as the modem may have rebooted correctly
|
||||
else:
|
||||
print("✅ Modem acknowledged reboot command.")
|
||||
|
||||
# Step 2: Wait for the modem to restart (adjust time as needed)
|
||||
print("Waiting for modem to restart...")
|
||||
time.sleep(15) # 15 seconds should be enough for most modems to restart
|
||||
time.sleep(7) # 7 seconds should be enough for most modems to restart
|
||||
|
||||
# Step 3: Check if modem is responsive after reboot
|
||||
print("Checking if modem is responsive...")
|
||||
ser_sara.write(b'AT\r')
|
||||
response_check = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
||||
if response_check is None or "OK" not in response_check:
|
||||
print("⚠️ Modem not responding after reboot")
|
||||
|
||||
for attempt in range(5):
|
||||
ser_sara.write(b'AT\r')
|
||||
response_check = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
||||
if response_check and "OK" in response_check:
|
||||
print("✅ Modem is responsive after reboot.")
|
||||
break
|
||||
print(f"⏳ Waiting for modem... attempt {attempt + 1}")
|
||||
time.sleep(2)
|
||||
else:
|
||||
print("❌ Modem not responding after reboot.")
|
||||
return False
|
||||
|
||||
print("✅ Modem restarted successfully")
|
||||
|
||||
# Step 4: Reset the HTTP Profile
|
||||
print('<span style="color: orange;font-weight: bold;">🔧 Resetting the HTTP Profile</span>')
|
||||
# Step 4: Reset AirCarto HTTP Profile
|
||||
print('<span style="color: orange;font-weight: bold;">🔧 Resetting AirCarto HTTP Profile</span>')
|
||||
#command = f'AT+UHTTP={aircarto_profile_id},1,"data.nebuleair.fr"\r'
|
||||
#ser_sara.write(command.encode('utf-8'))
|
||||
#responseResetHTTP = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5,wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
||||
#print('<p class="text-danger-emphasis">')
|
||||
#print(responseResetHTTP)
|
||||
#print("</p>", end="")
|
||||
|
||||
print("➡️SET URL")
|
||||
command = f'AT+UHTTP={aircarto_profile_id},1,"data.nebuleair.fr"\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
responseResetHTTP = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=5,
|
||||
wait_for_lines=["OK", "+CME ERROR"], debug=True)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(responseResetHTTP)
|
||||
print("</p>", end="")
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
http_reset_success = responseResetHTTP is not None and "OK" in responseResetHTTP
|
||||
http_reset_success = response_SARA_5 is not None and "OK" in response_SARA_5
|
||||
if not http_reset_success:
|
||||
print("⚠️ HTTP profile reset failed")
|
||||
print("⚠️ AirCarto HTTP profile reset failed")
|
||||
# Continue anyway, don't return False here
|
||||
|
||||
# Step 5: For SARA-R5, reset the PDP connection
|
||||
pdp_reset_success = True
|
||||
if modem_version == "SARA-R500":
|
||||
print("⚠️ Need to reset PDP connection for SARA-R500")
|
||||
|
||||
# Activate PDP context 1
|
||||
print('➡️ Activate PDP context 1')
|
||||
command = f'AT+CGACT=1,1\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp1 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_pdp1, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp1 is not None and "OK" in response_pdp1)
|
||||
if send_uSpot:
|
||||
print('<span style="color: orange;font-weight: bold;">🔧 Resetting uSpot HTTP Profile</span>')
|
||||
uSpot_profile_id = 1
|
||||
uSpot_url="api-prod.uspot.probesys.net"
|
||||
security_profile_id = 1
|
||||
|
||||
#step 1: import the certificate
|
||||
print("➡️ import certificate")
|
||||
certificate_name = "e6"
|
||||
with open("/var/www/nebuleair_pro_4g/SARA/SSL/certificate/e6.pem", "rb") as cert_file:
|
||||
certificate = cert_file.read()
|
||||
size_of_string = len(certificate)
|
||||
|
||||
# AT+USECMNG=0,<type>,<internal_name>,<data_size>
|
||||
# type-> 0 -> trusted root CA
|
||||
command = f'AT+USECMNG=0,0,"{certificate_name}",{size_of_string}\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"])
|
||||
print(response_SARA_1)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
print("➡️ add certificate")
|
||||
ser_sara.write(certificate)
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_2)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# op_code: 0 -> certificate validation level
|
||||
# param_val : 0 -> Level 0 No validation; 1-> Level 1 Root certificate validation
|
||||
print("➡️Set the security profile (params)")
|
||||
certification_level=0
|
||||
command = f'AT+USECPRF={security_profile_id},0,{certification_level}\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5b = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5b)
|
||||
time.sleep(0.5)
|
||||
|
||||
# op_code: 1 -> minimum SSL/TLS version
|
||||
# param_val : 0 -> any; server can use any version for the connection; 1-> LSv1.0; 2->TLSv1.1; 3->TLSv1.2;
|
||||
print("➡️Set the security profile (params)")
|
||||
minimum_SSL_version = 0
|
||||
command = f'AT+USECPRF={security_profile_id},1,{minimum_SSL_version}\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5bb = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5bb)
|
||||
time.sleep(0.5)
|
||||
|
||||
#op_code: 2 -> legacy cipher suite selection
|
||||
# 0 (factory-programmed value): a list of default cipher suites is proposed at the beginning of handshake process, and a cipher suite will be negotiated among the cipher suites proposed in the list.
|
||||
print("➡️Set cipher")
|
||||
cipher_suite = 0
|
||||
command = f'AT+USECPRF={security_profile_id},2,{cipher_suite}\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5cc = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5cc)
|
||||
time.sleep(0.5)
|
||||
|
||||
# op_code: 3 -> trusted root certificate internal name
|
||||
print("➡️Set the security profile (choose cert)")
|
||||
command = f'AT+USECPRF={security_profile_id},3,"{certificate_name}"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5c = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5c)
|
||||
time.sleep(0.5)
|
||||
|
||||
# op_code: 10 -> SNI (server name indication)
|
||||
print("➡️Set the SNI")
|
||||
command = f'AT+USECPRF={security_profile_id},10,"{uSpot_url}"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5cf = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5cf)
|
||||
time.sleep(0.5)
|
||||
|
||||
#step 4: set url (op_code = 1)
|
||||
print("➡️SET URL")
|
||||
command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5)
|
||||
time.sleep(1)
|
||||
|
||||
#step 4: set PORT (op_code = 5)
|
||||
print("➡️SET PORT")
|
||||
port = 443
|
||||
command = f'AT+UHTTP={uSpot_profile_id},5,{port}\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_55 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_55)
|
||||
time.sleep(1)
|
||||
|
||||
# Set the PDP type
|
||||
print('➡️ Set the PDP type to IPv4 referring to the output of the +CGDCONT read command')
|
||||
command = f'AT+UPSD=0,0,0\r'
|
||||
#step 4: set url to SSL (op_code = 6) (http_secure = 1 for HTTPS)(USECMNG_PROFILE = 2)
|
||||
print("➡️SET SSL")
|
||||
http_secure = 1
|
||||
command = f'AT+UHTTP={uSpot_profile_id},6,{http_secure},{security_profile_id}\r'
|
||||
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_pdp2, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp2 is not None and "OK" in response_pdp2)
|
||||
response_SARA_5fg = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5fg)
|
||||
time.sleep(1)
|
||||
|
||||
# Profile #0 is mapped on CID=1
|
||||
print('➡️ Profile #0 is mapped on CID=1.')
|
||||
command = f'AT+UPSD=0,100,1\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp3 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_pdp3, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp3 is not None and "OK" in response_pdp3)
|
||||
time.sleep(1)
|
||||
|
||||
# Activate the PSD profile
|
||||
print('➡️ Activate the PSD profile #0: the IPv4 address is already assigned by the network.')
|
||||
command = f'AT+UPSDA=0,3\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_pdp4 = read_complete_response(ser_sara, wait_for_lines=["OK", "+UUPSDA"])
|
||||
print(response_pdp4, end="")
|
||||
pdp_reset_success = pdp_reset_success and (response_pdp4 is not None and ("OK" in response_pdp4 or "+UUPSDA" in response_pdp4))
|
||||
time.sleep(1)
|
||||
|
||||
if not pdp_reset_success:
|
||||
print("⚠️ PDP connection reset had some issues")
|
||||
|
||||
# Return overall success
|
||||
return http_reset_success and pdp_reset_success
|
||||
@@ -491,17 +651,17 @@ try:
|
||||
|
||||
'''
|
||||
print('<h3>START LOOP</h3>')
|
||||
print(f'Modem version: {modem_version}')
|
||||
#print(f'Modem version: {modem_version}')
|
||||
|
||||
#Local timestamp
|
||||
#ATTENTION:
|
||||
# -> RTC module can be deconnected ""
|
||||
# -> RTC module can be out of time like "2000-01-01T00:55:21Z"
|
||||
print("➡️Getting local timestamp")
|
||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||
row = cursor.fetchone() # Get the first (and only) row
|
||||
rtc_time_str = row[1] # '2025-02-07 12:30:45' ou '2000-01-01 00:55:21' ou 'not connected'
|
||||
print(rtc_time_str)
|
||||
print(f"➡️Getting local timestamp: {rtc_time_str}")
|
||||
|
||||
|
||||
if rtc_time_str == 'not connected':
|
||||
print("⛔ Atttention RTC module not connected⛔")
|
||||
@@ -515,14 +675,14 @@ try:
|
||||
print("⛔ Attention: RTC has been reset to default date ⛔")
|
||||
rtc_status = "reset"
|
||||
else:
|
||||
print("✅ RTC timestamp is valid")
|
||||
#print("✅ RTC timestamp is valid")
|
||||
rtc_status = "valid"
|
||||
|
||||
# Always convert to InfluxDB format
|
||||
# Convert to InfluxDB RFC3339 format with UTC 'Z' suffix
|
||||
influx_timestamp = dt_object.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
rtc_status = "valid"
|
||||
print(influx_timestamp)
|
||||
#print(influx_timestamp)
|
||||
|
||||
#NEXTPM
|
||||
# We take the last measures (order by rowid and not by timestamp)
|
||||
@@ -628,6 +788,20 @@ try:
|
||||
#Wind meter
|
||||
if wind_meter:
|
||||
print("➡️Getting wind meter values")
|
||||
cursor.execute("SELECT * FROM data_WIND ORDER BY rowid DESC LIMIT 1")
|
||||
last_row = cursor.fetchone()
|
||||
if last_row:
|
||||
print("SQLite DB last available row:", last_row)
|
||||
wind_speed = last_row[1]
|
||||
wind_direction = last_row[2]
|
||||
|
||||
#Add data to payload CSV
|
||||
payload_csv[25] = wind_speed
|
||||
payload_csv[26] = wind_direction
|
||||
|
||||
else:
|
||||
print("No data available in the database.")
|
||||
|
||||
|
||||
#MPPT charger
|
||||
if mppt_charger:
|
||||
@@ -651,40 +825,67 @@ try:
|
||||
else:
|
||||
print("No data available in the database.")
|
||||
|
||||
print("Verify SARA R4 connection")
|
||||
#print("Verify SARA connection (AT)")
|
||||
|
||||
# Getting the LTE Signal (AT+CSQ)
|
||||
print("➡️Getting SARA LTE signal")
|
||||
|
||||
command = f'AT+CSQ\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response2 = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR", "+CME ERROR","Socket:bind"])
|
||||
|
||||
# Getting the LTE Signal
|
||||
print("➡️Getting LTE signal")
|
||||
ser_sara.write(b'AT+CSQ\r')
|
||||
response2 = read_complete_response(ser_sara, wait_for_lines=["OK", "ERROR", "+CME ERROR"])
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response2)
|
||||
print("</p>", end="")
|
||||
|
||||
|
||||
#Here it's possible that the SARA do not repond at all or send a error message
|
||||
#-> TO DO : harware reboot
|
||||
#-> send notification
|
||||
#-> hardware reboot
|
||||
#-> end loop, no need to continue
|
||||
|
||||
#1. No answer at all form SARA
|
||||
if response2 is None or response2 == "":
|
||||
print("No answer from SARA module")
|
||||
print("⚠️ATTENTION: No answer from SARA module")
|
||||
print('🛑STOP LOOP🛑')
|
||||
print("<hr>")
|
||||
|
||||
#Send notification (WIFI)
|
||||
send_error_notification(device_id, "serial_error")
|
||||
send_error_notification(device_id, "SERIAL ISSUE ->no answer from sara")
|
||||
#Hardware Reboot
|
||||
hardware_reboot_success = modem_hardware_reboot()
|
||||
if hardware_reboot_success:
|
||||
print("✅Modem successfully rebooted and reinitialized")
|
||||
else:
|
||||
print("⛔There were issues with the modem reboot/reinitialize process")
|
||||
|
||||
#end loop
|
||||
sys.exit()
|
||||
|
||||
#2. si on a une erreur
|
||||
#2. si on a une reponse du SARA mais c'est une erreur
|
||||
elif "+CME ERROR" in response2:
|
||||
print(f"SARA module returned error: {response2}")
|
||||
print("The CSQ command is not supported by this module or in its current state")
|
||||
print("⚠️ATTENTION: SARA is connected over serial but CSQ command not supported")
|
||||
print('🛑STOP LOOP🛑')
|
||||
print("<hr>")
|
||||
|
||||
#end loop
|
||||
sys.exit()
|
||||
|
||||
#3. On peut avoir une erreur de type "Socket:bind: Treck error 222 : Invalid argument"
|
||||
elif "Socket:bind: Treck error" in response2:
|
||||
print(f"SARA module returned error: {response2}")
|
||||
print("⚠️ATTENTION: low-level error from the Treck TCP/IP stack")
|
||||
print('🛑STOP LOOP🛑')
|
||||
print("<hr>")
|
||||
#Send notification (WIFI)
|
||||
send_error_notification(device_id, "SERIAL ISSUE -> Treck TCP/IP stack error")
|
||||
#hardware reboot
|
||||
hardware_reboot_success = modem_hardware_reboot()
|
||||
if hardware_reboot_success:
|
||||
print("✅Modem successfully rebooted and reinitialized")
|
||||
else:
|
||||
print("⛔There were issues with the modem reboot/reinitialize process")
|
||||
#end loop
|
||||
sys.exit()
|
||||
|
||||
@@ -706,7 +907,7 @@ try:
|
||||
command = f'AT+COPS=1,2,{selected_networkID}\r'
|
||||
#command = f'AT+COPS=0\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20)
|
||||
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=True)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(responseReconnect)
|
||||
print("</p>", end="")
|
||||
@@ -721,10 +922,15 @@ try:
|
||||
|
||||
|
||||
'''
|
||||
SEND TO AIRCARTO
|
||||
____ _____ _ _ ____ _ ___ ____ ____ _ ____ _____ ___
|
||||
/ ___|| ____| \ | | _ \ / \ |_ _| _ \ / ___| / \ | _ \_ _/ _ \
|
||||
\___ \| _| | \| | | | | / _ \ | || |_) | | / _ \ | |_) || || | | |
|
||||
___) | |___| |\ | |_| | / ___ \ | || _ <| |___ / ___ \| _ < | || |_| |
|
||||
|____/|_____|_| \_|____/ /_/ \_\___|_| \_\\____/_/ \_\_| \_\|_| \___/
|
||||
|
||||
'''
|
||||
|
||||
print('➡️<p class="fw-bold">SEND TO AIRCARTO SERVERS</p>')
|
||||
print('<p class="fw-bold">➡️SEND TO AIRCARTO SERVERS</p>', end="")
|
||||
# Write Data to saraR4
|
||||
# 1. Open sensordata_csv.json (with correct data size)
|
||||
csv_string = ','.join(str(value) if value is not None else '' for value in payload_csv)
|
||||
@@ -732,7 +938,7 @@ try:
|
||||
print("Open JSON:")
|
||||
command = f'AT+UDWNFILE="sensordata_csv.json",{size_of_string}\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=True)
|
||||
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=False)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_1)
|
||||
print("</p>", end="")
|
||||
@@ -742,22 +948,21 @@ try:
|
||||
#2. Write to shell
|
||||
print("Write data to memory:")
|
||||
ser_sara.write(csv_string.encode())
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=True)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_2)
|
||||
print("</p>", end="")
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||
print(f'<p class="text-danger-emphasis">{response_SARA_2.strip()}</p>', end="")
|
||||
|
||||
#3. Send to endpoint (with device ID)
|
||||
print("Send data (POST REQUEST):")
|
||||
command= f'AT+UHTTPC={aircarto_profile_id},4,"/pro_4G/data.php?sensor_id={device_id}&lat={device_latitude_raw}&long={device_longitude_raw}&datetime={influx_timestamp}","aircarto_server_response.txt","sensordata_csv.json",4\r'
|
||||
print("sending:")
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(command)
|
||||
print("</p>", end="")
|
||||
#print("sending:")
|
||||
#print('<p class="text-danger-emphasis">')
|
||||
#print(command)
|
||||
#print("</p>", end="")
|
||||
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
|
||||
response_SARA_3 = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=120, wait_for_lines=["+UUHTTPCR", "+CME ERROR", "ERROR"], debug=True)
|
||||
print("receiving:")
|
||||
#print("receiving:")
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_3)
|
||||
print("</p>", end="")
|
||||
@@ -842,16 +1047,36 @@ try:
|
||||
|
||||
# Extract just the error code
|
||||
error_code = extract_error_code(response_SARA_9)
|
||||
|
||||
if error_code is not None:
|
||||
# Display interpretation based on error code
|
||||
if error_code == 0:
|
||||
print('<p class="text-success">No error detected</p>')
|
||||
elif error_code == 4:
|
||||
print('<p class="text-danger">Error 4: Invalid server Hostname</p>')
|
||||
send_error_notification(device_id, "UHTTPER (error n°4) -> Invalid Server Hostname")
|
||||
server_hostname_resets = reset_server_hostname(aircarto_profile_id)
|
||||
if server_hostname_resets:
|
||||
print("✅server hostname reset successfully")
|
||||
else:
|
||||
print("⛔There were issues with the modem server hostname reinitialize process")
|
||||
|
||||
elif error_code == 11:
|
||||
print('<p class="text-danger">Error 11: Server connection error</p>')
|
||||
elif error_code == 22:
|
||||
print('<p class="text-danger">⚠️Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️</p>')
|
||||
send_error_notification(device_id, "UHTTPER (error n°22) -> PSD or CSD connection not established")
|
||||
psd_csd_resets = reset_PSD_CSD_connection()
|
||||
if psd_csd_resets:
|
||||
print("✅PSD CSD connection reset successfully")
|
||||
else:
|
||||
print("⛔There were issues with the modem CSD PSD reinitialize process")
|
||||
elif error_code == 26:
|
||||
print('<p class="text-danger">Error 26: Connection timed out</p>')
|
||||
send_error_notification(device_id, "UHTTPER (error n°26) -> Connection timed out")
|
||||
elif error_code == 44:
|
||||
print('<p class="text-danger">Error 44: Connection lost</p>')
|
||||
send_error_notification(device_id, "UHTTPER (error n°44) -> Connection lost")
|
||||
elif error_code == 73:
|
||||
print('<p class="text-danger">Error 73: Secure socket connect error</p>')
|
||||
else:
|
||||
@@ -861,11 +1086,11 @@ try:
|
||||
|
||||
|
||||
#Software Reboot
|
||||
software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||
if software_reboot_success:
|
||||
print("Modem successfully rebooted and reinitialized")
|
||||
else:
|
||||
print("There were issues with the modem reboot/reinitialize process")
|
||||
#software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||
#if software_reboot_success:
|
||||
# print("✅Modem successfully rebooted and reinitialized")
|
||||
#else:
|
||||
# print("⛔There were issues with the modem reboot/reinitialize process")
|
||||
|
||||
|
||||
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
|
||||
@@ -876,8 +1101,9 @@ try:
|
||||
led_thread.start()
|
||||
|
||||
#4. Read reply from server
|
||||
print("Reply from server:")
|
||||
ser_sara.write(b'AT+URDFILE="aircarto_server_response.txt"\r')
|
||||
print("Reply from server:")
|
||||
command = f'AT+URDFILE="aircarto_server_response.txt""\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_4 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||
print('<p class="text-success">')
|
||||
print(response_SARA_4)
|
||||
@@ -886,6 +1112,7 @@ try:
|
||||
#Parse the server datetime
|
||||
# Extract just the date from the response
|
||||
date_string = None
|
||||
server_datetime = ""
|
||||
date_start = response_SARA_4.find("Date: ")
|
||||
if date_start != -1:
|
||||
date_end = response_SARA_4.find("\n", date_start)
|
||||
@@ -952,7 +1179,7 @@ try:
|
||||
|
||||
|
||||
#Si non ne recoit pas de réponse UHTTPCR
|
||||
#on a peut etre une ERROR de type "+CME ERROR: No connection to phone" ou "Operation not allowed" ou "ERROR"
|
||||
#on a peut être une ERROR de type "+CME ERROR: No connection to phone" ou "Operation not allowed" ou "ERROR"
|
||||
else:
|
||||
print('<span style="color: red;font-weight: bold;">No UUHTTPCR response</span>')
|
||||
print("Blink red LED")
|
||||
@@ -1001,7 +1228,7 @@ try:
|
||||
if "ERROR" in line:
|
||||
print("⛔Attention ERROR!⛔")
|
||||
#Send notification (WIFI)
|
||||
send_error_notification(device_id, "sara_error")
|
||||
send_error_notification(device_id, "SARA CME ERROR")
|
||||
|
||||
#Software Reboot
|
||||
software_reboot_success = modem_complete_reboot_and_reinitialize(modem_version, aircarto_profile_id)
|
||||
@@ -1013,7 +1240,8 @@ try:
|
||||
|
||||
#5. empty json
|
||||
print("Empty SARA memory:")
|
||||
ser_sara.write(b'AT+UDELFILE="sensordata_csv.json"\r')
|
||||
command = f'AT+UDELFILE="sensordata_csv.json"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK","+CME ERROR"], debug=True)
|
||||
print('<p class="text-danger-emphasis">')
|
||||
print(response_SARA_5)
|
||||
@@ -1026,77 +1254,16 @@ try:
|
||||
|
||||
|
||||
'''
|
||||
SEND TO uSPOT
|
||||
_ ____ _
|
||||
___ ___ _ __ __| | _ _/ ___| _ __ ___ | |_
|
||||
/ __|/ _ \ '_ \ / _` | | | | \___ \| '_ \ / _ \| __|
|
||||
\__ \ __/ | | | (_| | | |_| |___) | |_) | (_) | |_
|
||||
|___/\___|_| |_|\__,_| \__,_|____/| .__/ \___/ \__|
|
||||
|_|
|
||||
'''
|
||||
|
||||
if send_uSpot:
|
||||
print('➡️<p class="fw-bold">SEND TO uSPOT SERVERS</p>')
|
||||
|
||||
if reset_uSpot_url:
|
||||
#2. Set uSpot URL (profile id = 1)
|
||||
print('➡️Set uSpot URL')
|
||||
uSpot_profile_id = 1
|
||||
uSpot_url="api-prod.uspot.probesys.net"
|
||||
security_profile_id = 1
|
||||
|
||||
#step 1: import the certificate
|
||||
print("****")
|
||||
certificate_name = "e6"
|
||||
with open("/var/www/nebuleair_pro_4g/SARA/SSL/certificate/e6.pem", "rb") as cert_file:
|
||||
certificate = cert_file.read()
|
||||
size_of_string = len(certificate)
|
||||
|
||||
print("\033[0;33m Import certificate\033[0m")
|
||||
# AT+USECMNG=0,<type>,<internal_name>,<data_size>
|
||||
# type-> 0 -> trusted root CA
|
||||
command = f'AT+USECMNG=0,0,"{certificate_name}",{size_of_string}\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_1 = read_complete_response(ser_sara)
|
||||
print(response_SARA_1)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
print("\033[0;33mAdd certificate\033[0m")
|
||||
ser_sara.write(certificate)
|
||||
response_SARA_2 = read_complete_response(ser_sara)
|
||||
print(response_SARA_2)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
# SECURITY PROFILE
|
||||
# op_code: 3 -> trusted root certificate internal name
|
||||
print("\033[0;33mSet the security profile (choose cert)\033[0m")
|
||||
command = f'AT+USECPRF={security_profile_id},3,"{certificate_name}"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_5c = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5c)
|
||||
time.sleep(0.5)
|
||||
|
||||
#step 4: set url (op_code = 1)
|
||||
command = f'AT+UHTTP={uSpot_profile_id},1,"{uSpot_url}"\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_2 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_2)
|
||||
time.sleep(1)
|
||||
|
||||
#step 4: set PORT (op_code = 5)
|
||||
print("set port 443")
|
||||
command = f'AT+UHTTP={uSpot_profile_id},5,443\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_55 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_55)
|
||||
time.sleep(1)
|
||||
|
||||
#step 4: set url to SSL (op_code = 6) (http_secure = 1 for HTTPS)(USECMNG_PROFILE = 2)
|
||||
print("\033[0;33mSET SSL\033[0m")
|
||||
http_secure = 1
|
||||
command = f'AT+UHTTP={uSpot_profile_id},6,{http_secure},{security_profile_id}\r'
|
||||
#command = f'AT+UHTTP={profile_id},6,{http_secure}\r'
|
||||
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"])
|
||||
print(response_SARA_5)
|
||||
time.sleep(1)
|
||||
print('<p class="fw-bold">➡️SEND TO uSPOT SERVERS</p>', end="")
|
||||
|
||||
# 1. Open sensordata_json.json (with correct data size)
|
||||
print("Open JSON:")
|
||||
@@ -1169,7 +1336,7 @@ try:
|
||||
led_thread.start()
|
||||
|
||||
# Get error code
|
||||
print("Getting error code")
|
||||
print("Getting error code", end="")
|
||||
command = f'AT+UHTTPER={uSpot_profile_id}\r'
|
||||
ser_sara.write(command.encode('utf-8'))
|
||||
response_SARA_9b = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||
@@ -1183,40 +1350,80 @@ try:
|
||||
if error_code == 0:
|
||||
print('<p class="text-success">No error detected</p>')
|
||||
elif error_code == 4:
|
||||
print('<p class="text-danger">Error 4: Invalid server Hostname</p>')
|
||||
print('<p class="text-danger">Error 4: Invalid server Hostname</p>', end="")
|
||||
send_error_notification(device_id, "UHTTPER (4) uSpot Invalid server Hostname")
|
||||
elif error_code == 11:
|
||||
print('<p class="text-danger">Error 11: Server connection error</p>')
|
||||
print('<p class="text-danger">Error 11: Server connection error</p>', end="")
|
||||
elif error_code == 22:
|
||||
print('<p class="text-danger">Error 22: PSD or CSD connection not established</p>')
|
||||
print('<p class="text-danger">Error 22: PSD or CSD connection not established</p>', end="")
|
||||
elif error_code == 26:
|
||||
print('<p class="text-danger">Error 26: Connection timed out</p>')
|
||||
elif error_code == 44:
|
||||
print('<p class="text-danger">Error 44: Connection lost</p>')
|
||||
elif error_code == 73:
|
||||
print('<p class="text-danger">Error 73: Secure socket connect error</p>')
|
||||
print('<p class="text-danger">Error 73: Secure socket connect error</p>', end="")
|
||||
send_error_notification(device_id, "uSpot - Secure socket connect error")
|
||||
#Software Reboot ??
|
||||
|
||||
else:
|
||||
print(f'<p class="text-danger">Unknown error code: {error_code}</p>')
|
||||
print(f'<p class="text-danger">Unknown error code: {error_code}</p>',end="")
|
||||
else:
|
||||
print('<p class="text-danger">Could not extract error code from response</p>')
|
||||
print('<p class="text-danger">Could not extract error code from response</p>', end="")
|
||||
|
||||
#Pas forcément un moyen de résoudre le soucis
|
||||
|
||||
# 2.2 code 1 (HHTP succeded)
|
||||
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
|
||||
else:
|
||||
# Si la commande HTTP a réussi
|
||||
print('<span style="font-weight: bold;">✅✅HTTP operation successful.</span>')
|
||||
print("Blink blue LED")
|
||||
led_thread = Thread(target=blink_led, args=(23, 5, 0.5))
|
||||
led_thread.start()
|
||||
|
||||
#4. Read reply from server
|
||||
print("Reply from server:")
|
||||
ser_sara.write(b'AT+URDFILE="uSpot_server_response.txt"\r')
|
||||
print("Reply from server:")
|
||||
command = f'AT+URDFILE="uSpot_server_response.txt"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_4b = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||
print('<p class="text-success">')
|
||||
print(response_SARA_4b)
|
||||
print("</p>", end="")
|
||||
|
||||
# Initialize http_response_code to 0 as a default value
|
||||
http_response_code = 0
|
||||
|
||||
# Safely extract HTTP code
|
||||
try:
|
||||
http_prefix = "HTTP/"
|
||||
# response_SARA_4b is a string, not a function - use .find() method
|
||||
http_pos = response_SARA_4b.find(http_prefix)
|
||||
|
||||
if http_pos != -1:
|
||||
# Find the space after the HTTP version
|
||||
space_pos = response_SARA_4b.find(" ", http_pos)
|
||||
if space_pos != -1:
|
||||
# Extract the code after the space
|
||||
code_start = space_pos + 1
|
||||
code_end = response_SARA_4b.find(" ", code_start)
|
||||
if code_end != -1:
|
||||
# Extract and convert to integer
|
||||
http_code_str = response_SARA_4b[code_start:code_end]
|
||||
http_response_code = int(http_code_str)
|
||||
print(f"HTTP response code: {http_response_code}")
|
||||
if http_response_code == 201:
|
||||
print('<span style="font-weight: bold;">✅✅HTTP 201 ressource created.</span>')
|
||||
|
||||
except Exception as e:
|
||||
# If any error occurs during parsing, keep the default value
|
||||
print(f"Error parsing HTTP code: {e}")
|
||||
|
||||
|
||||
|
||||
|
||||
#5. empty json
|
||||
print("Empty SARA memory:")
|
||||
ser_sara.write(b'AT+UDELFILE="sensordata_json.json"\r')
|
||||
command = f'AT+UDELFILE="sensordata_json.json"\r'
|
||||
ser_sara.write((command + '\r').encode('utf-8'))
|
||||
response_SARA_9t = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
|
||||
print(response_SARA_9t)
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ def run_script(script_name, interval, delay=0):
|
||||
if not is_script_locked():
|
||||
create_lock_file()
|
||||
try:
|
||||
subprocess.run(["python3", script_path])
|
||||
subprocess.run(["python3", script_path], timeout=200)
|
||||
finally:
|
||||
remove_lock_file()
|
||||
else:
|
||||
18
services/README.md
Normal file
18
services/README.md
Normal 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
|
||||
|
||||
39
services/check_services.sh
Normal file
39
services/check_services.sh
Normal 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
228
services/setup_services.sh
Normal 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"
|
||||
@@ -27,14 +27,6 @@ CREATE TABLE IF NOT EXISTS config_table (
|
||||
)
|
||||
""")
|
||||
|
||||
#creates a config_scripts table
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS config_scripts_table (
|
||||
script_path TEXT PRIMARY KEY,
|
||||
enabled INTEGER NOT NULL
|
||||
)
|
||||
''')
|
||||
|
||||
#creates a config table for envea sondes
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS envea_sondes_table (
|
||||
@@ -46,7 +38,6 @@ CREATE TABLE IF NOT EXISTS envea_sondes_table (
|
||||
)
|
||||
""")
|
||||
|
||||
|
||||
# Create a table timer
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS timestamp_table (
|
||||
|
||||
@@ -45,7 +45,7 @@ if row:
|
||||
print(f"[INFO] Deleting records older than: {cutoff_date_str}")
|
||||
|
||||
# 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
|
||||
for table in tables_to_clean:
|
||||
|
||||
@@ -20,37 +20,14 @@ cursor = conn.cursor()
|
||||
|
||||
print(f"Connected to database")
|
||||
|
||||
# Clear existing data (if any)
|
||||
cursor.execute("DELETE FROM config_table")
|
||||
cursor.execute("DELETE FROM config_scripts_table")
|
||||
cursor.execute("DELETE FROM envea_sondes_table")
|
||||
print("Existing data cleared")
|
||||
# Note: Using INSERT OR IGNORE to add only new configurations without overwriting existing ones
|
||||
print("Adding new configurations (existing ones will be preserved)")
|
||||
|
||||
#add values
|
||||
|
||||
# Insert script configurations
|
||||
script_configs = [
|
||||
("NPM/get_data_modbus_v3.py", True),
|
||||
("loop/SARA_send_data_v2.py", True),
|
||||
("RTC/save_to_db.py", True),
|
||||
("BME280/get_data_v2.py", True),
|
||||
("envea/read_value_v2.py", False),
|
||||
("MPPT/read.py", False),
|
||||
("windMeter/read.py", False),
|
||||
("sqlite/flush_old_data.py", True)
|
||||
]
|
||||
|
||||
for script_path, enabled in script_configs:
|
||||
cursor.execute(
|
||||
"INSERT INTO config_scripts_table (script_path, enabled) VALUES (?, ?)",
|
||||
(script_path, 1 if enabled else 0)
|
||||
)
|
||||
|
||||
# Insert general configurations
|
||||
config_entries = [
|
||||
("modem_config_mode", "0", "bool"),
|
||||
("deviceID", "XXXX", "str"),
|
||||
("npm_5channel", "0", "bool"),
|
||||
("latitude_raw", "0", "int"),
|
||||
("longitude_raw", "0", "int"),
|
||||
("latitude_precision", "0", "int"),
|
||||
@@ -65,12 +42,17 @@ config_entries = [
|
||||
("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 INTO config_table (key, value, type) VALUES (?, ?, ?)",
|
||||
"INSERT OR IGNORE INTO config_table (key, value, type) VALUES (?, ?, ?)",
|
||||
(key, value, value_type)
|
||||
)
|
||||
|
||||
@@ -83,7 +65,7 @@ envea_sondes = [
|
||||
|
||||
for connected, port, name, coefficient in envea_sondes:
|
||||
cursor.execute(
|
||||
"INSERT INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
|
||||
"INSERT OR IGNORE INTO envea_sondes_table (connected, port, name, coefficient) VALUES (?, ?, ?, ?)",
|
||||
(1 if connected else 0, port, name, coefficient)
|
||||
)
|
||||
|
||||
|
||||
118
update_firmware.sh
Normal file
118
update_firmware.sh
Normal 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
|
||||
@@ -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
|
||||
|
||||
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
|
||||
import time
|
||||
|
||||
Reference in New Issue
Block a user