Compare commits

...

19 Commits

Author SHA1 Message Date
Your Name
bc71a271eb update 2025-07-03 09:54:32 +01:00
root
29458c4841 update 2025-07-02 13:52:03 +02:00
Your Name
3b8b51172c update 2025-06-30 15:37:24 +01:00
Your Name
acdc736a38 update 2025-06-30 15:25:00 +01:00
Your Name
c5f120106f update 2025-06-30 15:20:00 +01:00
Your Name
93d0e3b9cf update 2025-06-30 15:06:48 +01:00
Your Name
5027f9945b update 2025-06-27 11:09:19 +02:00
Your Name
943243f769 update 2025-06-27 10:42:57 +02:00
root
5adc814e1b update 2025-06-24 18:13:34 +02:00
root
d481608c9a update 2025-06-24 15:44:11 +02:00
root
76cf2a30bf update 2025-06-24 12:15:23 +02:00
root
d8e004e9bd update 2025-06-24 12:12:21 +02:00
root
675482929e update 2025-06-24 11:53:59 +02:00
root
fea7c03bfb update 2025-06-24 11:28:52 +02:00
root
1b3cf16d55 update 2025-06-24 10:55:51 +02:00
root
677595835a update 2025-06-24 10:32:48 +02:00
root
806f95df75 update 2025-06-23 13:32:16 +02:00
root
96231582f5 Merge remote-tracking branch 'refs/remotes/origin/main' 2025-06-23 11:13:23 +02:00
root
45fa8b382a update 2025-06-23 11:12:21 +02:00
15 changed files with 1174 additions and 122 deletions

View File

@@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(sqlite3:*)"
],
"deny": []
},
"enableAllProjectMcpServers": false
}

74
BME280/get_data_v2.py Normal file
View File

@@ -0,0 +1,74 @@
'''
____ __ __ _____ ____ ___ ___
| __ )| \/ | ____|___ \( _ ) / _ \
| _ \| |\/| | _| __) / _ \| | | |
| |_) | | | | |___ / __/ (_) | |_| |
|____/|_| |_|_____|_____\___/ \___/
Script to read data from BME280
Sensor connected to i2c on address 76 (use sudo i2cdetect -y 1 to get the address )
-> save data to database (table data_BME280 )
sudo python3 /var/www/moduleair_pro_4g/BME280/get_data_v2.py
'''
import board
import busio
import json
import sqlite3
from adafruit_bme280 import basic as adafruit_bme280
# Connect to the SQLite database
conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
# Create I2C bus
i2c = busio.I2C(board.SCL, board.SDA)
bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c, address=0x76)
# Configure settings
bme280.sea_level_pressure = 1013.25 # Update this value for your location
# Read sensor data
#print(f"Temperature: {bme280.temperature:.2f} °C")
#print(f"Humidity: {bme280.humidity:.2f} %")
#print(f"Pressure: {bme280.pressure:.2f} hPa")
#print(f"Altitude: {bme280.altitude:.2f} m")
temperature = round(bme280.temperature, 2)
humidity = round(bme280.humidity, 2)
pressure = round(bme280.pressure, 2)
sensor_data = {
"temp": temperature, # Temperature in °C
"hum": humidity, # Humidity in %
"press": pressure # Pressure in hPa
}
#GET RTC TIME from SQlite
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'
# Convert to JSON and print
#print(json.dumps(sensor_data, indent=4))
#save to sqlite database
try:
cursor.execute('''
INSERT INTO data_BME280 (timestamp,temperature, humidity, pressure) VALUES (?,?,?,?)'''
, (rtc_time_str,temperature,humidity,pressure))
# Commit and close the connection
conn.commit()
#print("Sensor data saved successfully!")
except Exception as e:
print(f"Database error: {e}")
conn.close()

View File

@@ -30,7 +30,8 @@ sudo mkdir /var/www/moduleair_pro_4g/logs
sudo touch /var/www/moduleair_pro_4g/logs/app.log /var/www/moduleair_pro_4g/logs/loop.log /var/www/moduleair_pro_4g/matrix/input_NPM.txt /var/www/moduleair_pro_4g/matrix/input_MHZ16.txt /var/www/moduleair_pro_4g/wifi_list.csv
sudo cp /var/www/moduleair_pro_4g/config.json.dist /var/www/moduleair_pro_4g/config.json
sudo chmod -R 777 /var/www/moduleair_pro_4g/
git config core.fileMode false
git -C /var/www/moduleair_pro_4g config core.fileMode false
git config --global --add safe.directory /var/www/moduleair_pro_4g
```

View File

@@ -20,7 +20,7 @@ import re
import sqlite3
# database connection
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
#get config data from SQLite table
@@ -205,7 +205,7 @@ try:
# 1. Set AIRCARTO URL
print('Set aircarto URL')
aircarto_profile_id = 0
aircarto_url="data.nebuleair.fr"
aircarto_url="data.moduleair.fr"
command = f'AT+UHTTP={aircarto_profile_id},1,"{aircarto_url}"\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_1 = read_complete_response(ser_sara, wait_for_lines=["OK"])

View File

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

View File

@@ -155,7 +155,34 @@
<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="updateGitPull()">Update firmware (v1)</button>
<button type="submit" class="btn btn-primary" onclick="updateFirmware()" id="updateBtn">
<span id="updateBtnText">Update firmware (v2)</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>
@@ -285,11 +312,13 @@ window.onload = function() {
const checkbox_bme = document.getElementById("check_bme280");
const checkbox_CO2 = document.getElementById("check_CO2");
const checkbox_SFA30 = document.getElementById("check_sensirionSFA30");
const checkbox_uSpot = document.getElementById("check_uSpot");
checkbox_nmp5channels.checked = response.npm_5channel;
checkbox_bme.checked = response["BME280"];
checkbox_SFA30.checked = response["SFA30"];
checkbox_CO2.checked = response["MHZ19"];
checkbox_uSpot.checked = response["send_uSpot"];
//si sonde envea is true
if (response["envea"]) {
@@ -439,6 +468,108 @@ function updateGitPull(){
});
}
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=update_firmware',
method: 'GET',
dataType: 'json',
timeout: 120000, // 2 minutes timeout
success: function(response) {
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('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();
}
function set_RTC_withNTP(){
console.log("Set RTC module with WIFI (NTP server)");

View File

@@ -255,6 +255,20 @@ if ($type == "git_pull") {
echo $output;
}
if ($type == "update_firmware") {
// Execute the comprehensive update script
$command = 'sudo /var/www/moduleair_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/moduleair_pro_4g/RTC/set_with_NTP.py';
$output = shell_exec($command);

View File

@@ -61,9 +61,33 @@ check_sudo() {
print_success "Sudo privileges confirmed"
}
# Pre-configure packages to avoid prompts
pre_configure_packages() {
print_step "Pre-configuring packages to avoid prompts..."
# Pre-configure common package questions
# For any package that uses debconf
echo 'debconf debconf/frontend select Noninteractive' | sudo debconf-set-selections
# For timezone data (if tzdata gets updated)
echo 'tzdata tzdata/Areas select Europe' | sudo debconf-set-selections
echo 'tzdata tzdata/Zones/Europe select Paris' | sudo debconf-set-selections
# For keyboard configuration
echo 'keyboard-configuration keyboard-configuration/layoutcode string us' | sudo debconf-set-selections
# For any service restart prompts
echo '* libraries/restart-without-asking boolean true' | sudo debconf-set-selections
print_success "Package pre-configuration completed"
}
# System update
update_system() {
print_step "Updating system packages..."
# Set non-interactive mode for apt
export DEBIAN_FRONTEND=noninteractive
sudo apt update -y
print_success "System packages updated"
}
@@ -72,10 +96,14 @@ update_system() {
install_dependencies() {
print_step "Installing system dependencies..."
# Ensure non-interactive mode
export DEBIAN_FRONTEND=noninteractive
local packages=(
"git"
"apache2"
"php"
"php-sqlite3"
"sqlite3"
"python3"
"python3-pip"
@@ -88,11 +116,12 @@ install_dependencies() {
"python3-serial"
"python3-requests"
"python3-schedule"
"python3-rpi.gpio"
)
for package in "${packages[@]}"; do
print_status "Installing $package..."
if sudo apt install -y "$package"; then
if sudo DEBIAN_FRONTEND=noninteractive apt install -y -q "$package"; then
print_success "$package installed"
else
print_error "Failed to install $package"
@@ -112,7 +141,6 @@ install_python_packages() {
"pyserial"
"requests"
"schedule"
"RPi.GPIO"
"gpiozero"
"smbus2"
"adafruit-circuitpython-bme280"
@@ -123,7 +151,7 @@ install_python_packages() {
for package in "${pip_packages[@]}"; do
print_status "Installing Python package: $package..."
if pip3 install "$package" --break-system-packages; then
if sudo pip3 install "$package" --break-system-packages; then
print_success "$package installed"
else
print_warning "Failed to install $package (may already be installed)"
@@ -157,49 +185,6 @@ clone_repository() {
print_success "Repository permissions set"
}
# Setup directory structure
setup_directories() {
print_step "Setting up additional directories and files..."
# Create additional directories
local dirs=(
"sqlite"
"logs"
"matrix/input"
)
for dir in "${dirs[@]}"; do
sudo mkdir -p "/var/www/moduleair_pro_4g/$dir"
print_success "Created directory: $dir"
done
# Create required log files
local files=(
"logs/app.log"
"logs/loop.log"
"matrix/input_NPM.txt"
"matrix/input_MHZ16.txt"
"wifi_list.csv"
)
for file in "${files[@]}"; do
sudo touch "/var/www/moduleair_pro_4g/$file"
print_success "Created file: $file"
done
# Copy config template if it exists
if [ -f "/var/www/moduleair_pro_4g/config.json.dist" ]; then
sudo cp /var/www/moduleair_pro_4g/config.json.dist /var/www/moduleair_pro_4g/config.json
print_success "Configuration file created from template"
else
print_warning "config.json.dist not found - skipping config file creation"
fi
# Set proper permissions
sudo chown -R www-data:www-data /var/www/moduleair_pro_4g
sudo chmod -R 755 /var/www/moduleair_pro_4g
print_success "Directory permissions updated"
}
# Configure Apache
configure_apache() {
@@ -222,6 +207,70 @@ configure_apache() {
print_success "Apache configured and restarted"
}
# System optimizations (disable audio, isolate CPU)
system_optimizations() {
print_step "Applying system optimizations..."
# Find config.txt location
local config_file="/boot/firmware/config.txt"
if [ ! -f "$config_file" ]; then
config_file="/boot/config.txt"
fi
if [ ! -f "$config_file" ]; then
print_error "Could not find config.txt file"
return 1
fi
# Disable onboard audio
print_status "Disabling onboard audio..."
if grep -q "^dtparam=audio=" "$config_file"; then
# Replace existing audio parameter
sudo sed -i 's/^dtparam=audio=.*/dtparam=audio=off/' "$config_file"
print_success "Audio parameter updated to off"
else
# Add audio parameter if not exists
echo "dtparam=audio=off" | sudo tee -a "$config_file" > /dev/null
print_success "Audio disabled in config.txt"
fi
# Blacklist audio module
print_status "Blacklisting audio module..."
echo "blacklist snd_bcm2835" | sudo tee /etc/modprobe.d/blacklist.conf > /dev/null
print_success "Audio module blacklisted"
# Find cmdline.txt location
local cmdline_file="/boot/firmware/cmdline.txt"
if [ ! -f "$cmdline_file" ]; then
cmdline_file="/boot/cmdline.txt"
fi
if [ ! -f "$cmdline_file" ]; then
print_error "Could not find cmdline.txt file"
return 1
fi
# Add CPU isolation
print_status "Setting up CPU isolation..."
if ! grep -q "isolcpus=3" "$cmdline_file"; then
# Read current cmdline and append isolcpus
local current_cmdline=$(cat "$cmdline_file")
echo "${current_cmdline} isolcpus=3" | sudo tee "$cmdline_file" > /dev/null
print_success "CPU core 3 isolated"
else
print_warning "CPU isolation already configured"
fi
# Check if audio module is currently loaded
if lsmod | grep -q snd_bcm2835; then
print_warning "Audio module is currently loaded. It will be disabled after reboot."
else
print_success "Audio module is not loaded"
fi
print_success "System optimizations completed"
}
# Enable hardware interfaces
enable_hardware() {
print_step "Enabling hardware interfaces..."
@@ -283,6 +332,58 @@ enable_hardware() {
print_success "Hardware interfaces configuration completed"
}
# Setup sudo authorization
setup_sudo_authorization() {
print_step "Setting up sudo authorization..."
SUDOERS_FILE="/etc/sudoers"
# First, fix any existing syntax errors
if sudo visudo -c 2>&1 | grep -q "syntax error"; then
print_warning "Syntax error detected in sudoers file. Attempting to fix..."
# Remove the problematic line if it exists
sudo sed -i '/www-data ALL=(ALL) NOPASSWD: \/usr\/bin\/python3 \* www-data/d' "$SUDOERS_FILE"
fi
# Add proper sudo rules (each on a separate line)
if ! sudo grep -q "/usr/bin/nmcli" "$SUDOERS_FILE"; then
# Create a temporary file with the new rules
cat <<EOF | sudo tee /tmp/sudoers_additions > /dev/null
# ModuleAir Pro 4G sudo rules
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/moduleair_pro_4g/*
EOF
# Validate the temporary file
if sudo visudo -c -f /tmp/sudoers_additions; then
# Append to sudoers if valid using tee with sudo
cat /tmp/sudoers_additions | sudo EDITOR='tee -a' visudo >/dev/null 2>&1
print_success "Sudo authorization added"
else
print_error "Failed to add sudo rules - syntax validation failed"
sudo rm -f /tmp/sudoers_additions
return 1
fi
# Clean up
sudo rm -f /tmp/sudoers_additions
else
print_warning "Sudo authorization already set. Skipping."
fi
# Validate sudoers file after changes
if ! sudo visudo -c; then
print_error "Sudoers file has syntax errors! Please fix manually with 'sudo visudo'"
return 1
fi
print_success "Sudo authorization configured successfully"
}
# Set device permissions
set_permissions() {
print_step "Setting device permissions..."
@@ -301,36 +402,137 @@ set_permissions() {
print_success "Device permissions set"
}
# Check matrix binaries
check_matrix_binaries() {
print_step "Checking matrix display binaries..."
# Setup RTC save to DB service
setup_rtc_service() {
print_step "Setting up RTC save to DB service..."
local programs=(
"screenNetwork/network_status"
"screenSensors/displayAll4_v2"
"screenSensors/displayCO2_PM_Network"
"screenSensors/blank_screen"
"welcomeScreen/welcome_screen"
)
# Create the service file
cat << 'EOF' | sudo tee /etc/systemd/system/rtc_save_to_db.service > /dev/null
[Unit]
Description=RTC Save to DB Script
After=network.target
for program in "${programs[@]}"; do
local binary_file="/var/www/moduleair_pro_4g/matrix/${program}"
[Service]
ExecStart=/usr/bin/python3 /var/www/moduleair_pro_4g/RTC/save_to_db.py
Restart=always
RestartSec=1
User=root
WorkingDirectory=/var/www/moduleair_pro_4g
StandardOutput=append:/var/www/moduleair_pro_4g/logs/rtc_save_to_db.log
StandardError=append:/var/www/moduleair_pro_4g/logs/rtc_save_to_db_errors.log
if [ -f "$binary_file" ] && [ -x "$binary_file" ]; then
print_success "Matrix binary found: $program"
else
print_warning "Matrix binary missing or not executable: $program"
fi
done
[Install]
WantedBy=multi-user.target
EOF
# Check matrix library
if [ -f "/var/www/moduleair_pro_4g/matrix/lib/librgbmatrix.a" ]; then
print_success "Matrix library found: librgbmatrix.a"
print_success "RTC service file created"
# Create logs directory if it doesn't exist
sudo mkdir -p /var/www/moduleair_pro_4g/logs
sudo chown -R www-data:www-data /var/www/moduleair_pro_4g/logs
print_status "Logs directory created/verified"
# Reload systemd daemon
print_status "Reloading systemd daemon..."
sudo systemctl daemon-reload
# Enable the service
print_status "Enabling RTC service..."
if sudo systemctl enable rtc_save_to_db.service; then
print_success "RTC service enabled"
else
print_warning "Matrix library missing: librgbmatrix.a"
print_error "Failed to enable RTC service"
return 1
fi
print_success "Matrix binaries check completed"
# Start the service
print_status "Starting RTC service..."
if sudo systemctl start rtc_save_to_db.service; then
print_success "RTC service started"
else
print_warning "Failed to start RTC service - may need reboot"
fi
# Check service status
print_status "Checking RTC service status..."
if systemctl is-active --quiet rtc_save_to_db.service; then
print_success "RTC service is running"
else
print_warning "RTC service is not running - will start after reboot"
fi
return 0
}
# Setup cron jobs
setup_cron_jobs() {
print_step "Setting up cron jobs..."
if [ -f "/var/www/moduleair_pro_4g/cron_jobs" ]; then
if sudo crontab "/var/www/moduleair_pro_4g/cron_jobs"; then
print_success "Cron jobs set up successfully"
else
print_error "Failed to set up cron jobs"
return 1
fi
else
print_warning "Cron jobs file not found. Skipping."
fi
}
# Create databases
create_databases() {
print_step "Creating databases..."
if [ -f "/var/www/moduleair_pro_4g/sqlite/create_db.py" ]; then
if sudo /usr/bin/python3 "/var/www/moduleair_pro_4g/sqlite/create_db.py"; then
print_success "Databases created successfully"
else
print_error "Failed to create databases"
return 1
fi
else
print_warning "Database creation script not found"
fi
}
# Fix database permissions
fix_database_permissions() {
print_step "Fixing database permissions..."
# Set full permissions for sqlite directory
if [ -d "/var/www/moduleair_pro_4g/sqlite" ]; then
sudo chmod 777 /var/www/moduleair_pro_4g/sqlite/
print_success "Set permissions for sqlite directory"
else
print_warning "sqlite directory not found"
fi
# Set full permissions for sensors.db
if [ -f "/var/www/moduleair_pro_4g/sqlite/sensors.db" ]; then
sudo chmod 777 /var/www/moduleair_pro_4g/sqlite/sensors.db
print_success "Set permissions for sensors.db"
else
print_warning "sensors.db not found"
fi
print_success "Database permissions fixed"
}
# Set configuration
set_configuration() {
print_step "Setting configuration..."
if [ -f "/var/www/moduleair_pro_4g/sqlite/set_config_smart.py" ]; then
if sudo /usr/bin/python3 "/var/www/moduleair_pro_4g/sqlite/set_config_smart.py"; then
print_success "Configuration set successfully"
else
print_error "Failed to set configuration"
return 1
fi
else
print_warning "Configuration script not found"
fi
}
# Setup systemd services
@@ -403,6 +605,7 @@ final_status() {
"moduleair-sara-data.timer"
"moduleair-bme-data.timer"
"moduleair-boot.service"
"rtc_save_to_db.service"
)
for service in "${services[@]}"; do
@@ -420,9 +623,15 @@ final_status() {
echo -e "${GREEN}${NC} Directory structure created"
echo -e "${GREEN}${NC} Apache web server configured"
echo -e "${GREEN}${NC} Hardware interfaces enabled"
echo -e "${GREEN}${NC} System optimizations applied"
echo -e "${GREEN}${NC} Device permissions set"
echo -e "${GREEN}${NC} Sudo authorization configured"
echo -e "${GREEN}${NC} Databases created"
echo -e "${GREEN}${NC} Configuration set"
echo -e "${GREEN}${NC} Cron jobs configured"
echo -e "${GREEN}${NC} Matrix binaries verified"
echo -e "${GREEN}${NC} Systemd services configured"
echo -e "${GREEN}${NC} RTC save to DB service installed"
echo -e "${GREEN}${NC} Boot scripts created"
print_header "NEXT STEPS"
@@ -434,6 +643,7 @@ final_status() {
echo -e ""
echo -e "${YELLOW}3.${NC} Check service status:"
echo -e " ${CYAN}systemctl list-timers | grep moduleair${NC}"
echo -e " ${CYAN}systemctl status rtc_save_to_db.service${NC}"
echo -e ""
echo -e "${YELLOW}4.${NC} Test RTC module (after reboot):"
echo -e " ${CYAN}python3 /var/www/moduleair_pro_4g/RTC/read.py${NC}"
@@ -443,8 +653,19 @@ final_status() {
echo -e ""
echo -e "${YELLOW}6.${NC} Check logs if needed:"
echo -e " ${CYAN}tail -f /var/www/moduleair_pro_4g/logs/*.log${NC}"
echo -e " ${CYAN}tail -f /var/www/moduleair_pro_4g/logs/rtc_save_to_db.log${NC}"
echo -e ""
echo -e "${YELLOW}7.${NC} Check cron jobs:"
echo -e " ${CYAN}sudo crontab -l${NC}"
echo -e ""
echo -e "${YELLOW}8.${NC} Verify audio is disabled (after reboot):"
echo -e " ${CYAN}lsmod | grep snd_bcm2835${NC} (should return nothing)"
echo -e ""
echo -e "${YELLOW}9.${NC} Verify CPU isolation (after reboot):"
echo -e " ${CYAN}cat /proc/cmdline | grep isolcpus${NC}"
echo -e ""
print_warning "I2C and UART devices will NOT work until after reboot!"
print_warning "Audio and CPU isolation changes require reboot to take effect!"
}
# Main installation function
@@ -452,18 +673,31 @@ main() {
print_header "MODULEAIR PRO 4G INSTALLATION"
print_status "Starting automated installation..."
# Set non-interactive mode globally
export DEBIAN_FRONTEND=noninteractive
export NEEDRESTART_MODE=a
check_root
check_sudo
pre_configure_packages
update_system
install_dependencies
install_python_packages
clone_repository
setup_directories
configure_apache
enable_hardware
system_optimizations
set_permissions
check_matrix_binaries
setup_sudo_authorization
# Database and configuration setup
create_databases
set_configuration
fix_database_permissions # Fix permissions after database creation
setup_cron_jobs
setup_services
setup_rtc_service # New function for RTC service
create_boot_script
final_status

View File

@@ -88,7 +88,7 @@ JSON PAYLOAD (Micro-Spot Servers)
}
"""
import board
#import board
import json
import serial
import time
@@ -509,12 +509,134 @@ def reset_server_hostname(profile_id):
if not http_reset_success:
print("⚠️ AirCarto HTTP profile reset failed")
elif profile_id ==1:
pass # TODO: implement handling for profile 1
pass # on utilise la fonction reset_server_hostname_https pour uSpot
else:
print(f"❌ Unsupported profile ID: {profile_id}")
http_reset_success = False
return http_reset_success
def reset_server_hostname_https(profile_id):
"""
Function that reset server hostname (URL) connection for the SARA R5
returns true or false
"""
print("Reseting Server Hostname HTTS secure connection ")
http_reset_success = False # Default fallback
#Pour uSpot
if profile_id == 1:
print('<span style="color: orange;font-weight: bold;">🔧 Resetting uSpot HTTPs Profile</span>')
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/moduleair_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={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={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)
#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={profile_id},6,{http_secure},{security_profile_id}\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_5fg = read_complete_response(ser_sara, wait_for_lines=["OK"])
print(response_SARA_5fg)
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")
#Pour uSpot
elif profile_id ==1:
pass #on utilise la fonction reset_server_hostname_https pour uSpot
else:
print(f"❌ Unsupported profile ID: {profile_id}")
http_reset_success = False
return http_reset_success
try:
'''
_ ___ ___ ____
@@ -524,7 +646,7 @@ try:
|_____\___/ \___/|_|
'''
print('<h3>START LOOP</h3>')
print('<h3>START LOOP</h3>', end="")
# Check system uptime
with open('/proc/uptime', 'r') as f:
@@ -654,7 +776,7 @@ try:
payload_csv[20] = co2_average # Choose appropriate index
# Add data to payload JSON
payload_json["sensordatavalues"].append({"value_type": "CO2", "value": str(co2_average)})
payload_json["sensordatavalues"].append({"value_type": "MHZ16_CO2", "value": str(co2_average)})
print(f"CO2 average from {len(co2_values)} measurements: {co2_average}")
else:
@@ -732,19 +854,19 @@ try:
time.sleep(0.1)
# On vérifie si le signal n'est pas à 99 pour déconnexion
# si c'est le cas on essaie de se reconnecter
if signal_quality == 99:
update_config_sqlite('SARA_network_status', 'disconnected')
update_config_sqlite('SARA_signal_quality', '99')
print('<span style="color: red;font-weight: bold;">⚠ATTENTION: Signal Quality indicates no signal (99)⚠️</span>')
print("TRY TO RECONNECT:")
command = f'AT+COPS=1,2,"{selected_networkID}"\r'
ser_sara.write(command.encode('utf-8'))
responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=True)
print('<p class="text-danger-emphasis">')
print(responseReconnect)
print("</p>", end="")
#print("TRY TO RECONNECT:")
#command = f'AT+COPS=1,2,"{selected_networkID}"\r'
#ser_sara.write(command.encode('utf-8'))
#responseReconnect = read_complete_response(ser_sara, timeout=20, end_of_response_timeout=20, wait_for_lines=["OK", "+CME ERROR", "ERROR"], debug=True)
#print('<p class="text-danger-emphasis">')
#print(responseReconnect)
#print("</p>", end="")
print('🛑STOP LOOP🛑')
print("<hr>")
@@ -816,7 +938,7 @@ try:
# 1.Vérifier si la réponse contient un message d'erreur CME
if "+CME ERROR" in lines[-1]:
print("*****")
print('<span style="color: red;font-weight: bold;">ATTENTION: CME ERROR</span>')
print('<span style="color: red;font-weight: bold;">ATTENTION: CME ERROR</span>')
print("error:", lines[-1])
print("*****")
@@ -851,16 +973,16 @@ try:
# 2.1 code 0 (HTTP failed) ⛔⛔⛔
if len(parts) == 3 and parts[-1] == '0': # The third value indicates success
print("*****")
print('<span style="color: red;font-weight: bold;">ATTENTION: HTTP operation failed</span>')
print('<span style="color: red;font-weight: bold;">ATTENTION: HTTP operation failed</span>')
print("*****")
update_config_sqlite('SARA_network_status', 'error')
# Get error code
print("Getting error code")
print("Getting error code", end="")
command = f'AT+UHTTPER={aircarto_profile_id}\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_9 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
response_SARA_9 = read_complete_response(ser_sara, wait_for_lines=["OK","ERROR"], debug=False)
print('<p class="text-danger-emphasis">')
print(response_SARA_9)
print("</p>", end="")
@@ -871,50 +993,57 @@ try:
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>')
print('<p class="text-success">No error detected</p>', end="")
# N°4 INVALID SERVER HOSTNAME
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 (error n°4) -> Invalid Server Hostname")
server_hostname_resets = reset_server_hostname(aircarto_profile_id)
if server_hostname_resets:
print("✅server hostname reset successfully")
print("✅server hostname reset successfully", end="")
else:
print("⛔There were issues with the modem server hostname reinitialize process")
# N°11 SERVER CONNECTION ERROR
elif error_code == 11:
print('<p class="text-danger">Error 11: Server connection error</p>')
print('<p class="text-danger">Error 11: AirCarto - Server connection error</p>', end="")
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")
# N°22 PSD CSD CONNECTION NOT ESTABLISHED
elif error_code == 22:
print('<p class="text-danger">⚠Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️</p>')
print('<p class="text-danger">⚠Error 22: PSD or CSD connection not established (SARA-R5 need to reset PDP conection)⚠️</p>', end="")
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")
print("✅PSD CSD connection reset successfully", end="")
else:
print("⛔There were issues with the modem CSD PSD reinitialize process")
print("⛔There were issues with the modem CSD PSD reinitialize process", end="")
# N°26 CONNECTION TIMED OUT
elif error_code == 26:
print('<p class="text-danger">Error 26: Connection timed out</p>')
print('<p class="text-danger">Error 26: Connection timed out</p>', end="")
send_error_notification(device_id, "UHTTPER (error n°26) -> Connection timed out")
# N°26 CONNECTION LOST
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>')
print('<p class="text-danger">Error 73: Secure socket connect error</p>', end="")
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="")
if "ERROR" in response_SARA_9:
print('<p class="text-danger">⚠Sara Module returned an error</p>',end="")
print('<p class="text-warning">⚠set up hardware reboot here???</p>',end="")
#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")
# 2.2 code 1 (✅✅HHTP / UUHTTPCR succeded✅✅)
else:
# Si la commande HTTP a réussi
print('<span class="badge text-bg-success">✅✅HTTP operation successful.</span>')
print('<span class="badge text-bg-success">✅✅HTTP operation successful.</span>',end="")
#update SARA_network_status
update_config_sqlite('SARA_network_status', 'connected')
@@ -972,12 +1101,12 @@ try:
server_time_formatted = server_datetime.strftime('%Y-%m-%d %H:%M:%S')
#update RTC module do not wait for answer, non blocking
#/usr/bin/python3 /var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39'
#/usr/bin/python3 /var/www/moduleair_pro_4g/RTC/set_with_browserTime.py '2024-01-30 12:48:39'
# Launch RTC update script as non-blocking subprocess
import subprocess
update_command = [
"/usr/bin/python3",
"/var/www/nebuleair_pro_4g/RTC/set_with_browserTime.py",
"/var/www/moduleair_pro_4g/RTC/set_with_browserTime.py",
server_time_formatted
]
@@ -1045,10 +1174,16 @@ try:
# print("Modem successfully rebooted and reinitialized")
#else:
# print("There were issues with the modem reboot/reinitialize process")
#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")
#5. empty json
print("Empty SARA memory:")
print("Empty SARA memory:", end="")
ser_sara.write(b'AT+UDELFILE="sensordata_csv.json"\r')
response_SARA_5 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
print('<p class="text-danger-emphasis">')
@@ -1058,11 +1193,191 @@ try:
if "+CME ERROR" in response_SARA_5:
print("⛔ Attention CME ERROR ⛔")
'''
_ ____ _
___ ___ _ __ __| | _ _/ ___| _ __ ___ | |_
/ __|/ _ \ '_ \ / _` | | | | \___ \| '_ \ / _ \| __|
\__ \ __/ | | | (_| | | |_| |___) | |_) | (_) | |_
|___/\___|_| |_|\__,_| \__,_|____/| .__/ \___/ \__|
|_|
'''
if send_uSpot:
print('<p class="fw-bold">➡SEND TO uSPOT SERVERS</p>', end="")
# 1. Open sensordata_json.json (with correct data size)
print("Open JSON:")
payload_string = json.dumps(payload_json) # Convert dict to JSON string
size_of_string = len(payload_string)
command = f'AT+UDWNFILE="sensordata_json.json",{size_of_string}\r'
ser_sara.write((command + '\r').encode('utf-8'))
response_SARA_6 = read_complete_response(ser_sara, wait_for_lines=[">"], debug=False)
print(response_SARA_6)
time.sleep(1)
#2. Write to shell
print("Write to memory:")
ser_sara.write(payload_string.encode())
response_SARA_7 = read_complete_response(ser_sara, wait_for_lines=["OK"], debug=False)
print(response_SARA_7)
#step 4: trigger the request (http_command=1 for GET and http_command=1 for POST)
print("****")
print("Trigger POST REQUEST")
command = f'AT+UHTTPC={uSpot_profile_id},4,"/moduleair?token=2AFF6dQk68daFZ","uSpot_server_response.txt","sensordata_json.json",4\r'
ser_sara.write(command.encode('utf-8'))
response_SARA_8 = read_complete_response(ser_sara, timeout=5, end_of_response_timeout=120, wait_for_lines=["+UUHTTPCR", "+CME ERROR"], debug=True)
print('<p class="text-danger-emphasis">')
print(response_SARA_8)
print("</p>", end="")
# si on recoit la réponse UHTTPCR
if "+UUHTTPCR" in response_SARA_8:
print("✅ Received +UUHTTPCR response.")
lines = response_SARA_8.strip().splitlines()
# 1.Vérifier si la réponse contient un message d'erreur CME
if "+CME ERROR" in lines[-1]:
print("*****")
print('<span style="color: red;font-weight: bold;">⛔ATTENTION: CME ERROR</span>')
print("error:", lines[-1])
print("*****")
#update status
# Gestion de l'erreur spécifique
if "No connection to phone" in lines[-1]:
print("No connection to the phone.")
elif "Operation not allowed" in lines[-1]:
print("Operation not allowed. This may require a different configuration.")
# Actions spécifiques pour ce type d'erreur
else:
# 2.Si la réponse contient une réponse HTTP valide
# Extract HTTP response code from the last line
# ATTENTION: lines[-1] renvoie l'avant dernière ligne et il peut y avoir un soucis avec le OK
# rechercher plutot
http_response = lines[-1] # "+UUHTTPCR: 0,4,0"
parts = http_response.split(',')
# 2.1 code 0 (HTTP failed)
if len(parts) == 3 and parts[-1] == '0': # The third value indicates success
print("*****")
print('<span style="color: red;font-weight: bold;">⛔ATTENTION: HTTP operation failed</span>')
print("*****")
# Get 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)
print('<p class="text-danger-emphasis">')
print(response_SARA_9b)
print("</p>", end="")
# Extract just the error code
error_code = extract_error_code(response_SARA_9b)
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>')
# INVALID SERVER HOSTNAME
elif error_code == 4:
print('<p class="text-danger">Error 4: Invalid server Hostname</p>', end="")
send_error_notification(device_id, "UHTTPER (4) uSpot Invalid server Hostname")
server_hostname_resets = reset_server_hostname_https(uSpot_profile_id)
if server_hostname_resets:
print("✅server hostname reset successfully")
else:
print("⛔There were issues with the modem server hostname reinitialize process")
# SERVER CONNECTION ERROR
elif error_code == 11:
print('<p class="text-danger">Error 11: uSpot - Server connection error</p>', end="")
elif error_code == 22:
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>', 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>',end="")
else:
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 / UUHTTPCR succeded✅✅)
else:
# Si la commande HTTP a réussi
print('<span style="font-weight: bold;">✅✅HTTP operation successful.</span>')
#4. Read reply from server
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>')
elif http_response_code == 308:
print('<span style="font-weight: bold;"> ⚠HTTP 308 Redirect, need to set up HTTPS.</span>')
server_hostname_resets = reset_server_hostname_https(uSpot_profile_id)
if server_hostname_resets:
print("✅server hostname reset successfully")
else:
print("⛔There were issues with the modem server hostname reinitialize process")
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:", end="")
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)
# Calculate and print the elapsed time
elapsed_time = time.time() - start_time_script
print(f"Elapsed time: {elapsed_time:.2f} seconds")
print("<hr>")
print("<hr>", end="")
print("<hr>", end="")
except Exception as e:
print("An error occurred:", e)

View File

@@ -7,14 +7,16 @@
Script to launch the welcome screen
Avant de tester:
sudo systemctl stop moduleair-boot.service
Pour compiler:
g++ -Iinclude -Llib welcome_screen.cc -o welcome_screen -lrgbmatrix
g++ -I /var/www/moduleair_pro_4g/matrix/include -L /var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/welcome_screen.cc -o /var/www/moduleair_pro_4g/matrix/welcome_screen -lrgbmatrix
g++ -I /var/www/moduleair_pro_4g/matrix/include -L /var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen_FM6126.cc -o /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen_FM6126 -lrgbmatrix
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen
sudo /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen_FM6126
*/
@@ -54,7 +56,7 @@ void draw_welcome_screen(Canvas *canvas) {
int letter_spacing = 0;
int x = 10, y1 = 20, y2 = 50;
rgb_matrix::DrawText(canvas, font2, x, y1, myCYAN, &bg_color, "Welcome to", letter_spacing);
rgb_matrix::DrawText(canvas, font2, x, y1, myCYAN, &bg_color, "PLOUF", letter_spacing);
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, "ModuleAir", letter_spacing);
usleep(2000000); // Display the welcome screen for 2 seconds

Binary file not shown.

View File

@@ -0,0 +1,100 @@
/*
__ __ _ _____ ____ _____ __
| \/ | / \|_ _| _ \|_ _\ \/ /
| |\/| | / _ \ | | | |_) || | \ /
| | | |/ ___ \| | | _ < | | / \
|_| |_/_/ \_\_| |_| \_\___/_/\_\
Script to launch the welcome screen
Avant de tester:
sudo systemctl stop moduleair-boot.service
Pour compiler:
g++ -I /var/www/moduleair_pro_4g/matrix/include -L /var/www/moduleair_pro_4g/matrix/lib /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen_ICN2037.cc -o /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen_ICN2037 -lrgbmatrix
Pour lancer:
sudo /var/www/moduleair_pro_4g/matrix/welcomeScreen/welcome_screen_ICN2037 \
--led-row-addr-type=2 \
--led-slowdown-gpio=4 \
--led-multiplexing=0
*/
#include "led-matrix.h"
#include "graphics.h"
#include <unistd.h>
#include <string.h>
#include <fstream>
#include <iostream>
#include <signal.h>
#include <atomic>
using rgb_matrix::RGBMatrix;
using rgb_matrix::Canvas;
std::atomic<bool> running(true);
void signal_handler(int signum) {
running = false;
}
//message d'accueil
void draw_welcome_screen(Canvas *canvas) {
canvas->Clear();
rgb_matrix::Color myCYAN(0, 255, 255);
rgb_matrix::Color myWHITE(255, 255, 255);
rgb_matrix::Color bg_color(0, 0, 0);
rgb_matrix::Font font1;
rgb_matrix::Font font2;
if (!font1.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) return;
if (!font2.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf")) return;
int letter_spacing = 0;
int x = 10, y1 = 20, y2 = 50;
rgb_matrix::DrawText(canvas, font2, x, y1, myCYAN, &bg_color, "PLOUF", letter_spacing);
rgb_matrix::DrawText(canvas, font2, x, y2, myWHITE, &bg_color, "ModuleAir", letter_spacing);
usleep(2000000); // Display the welcome screen for 2 seconds
}
int main(int argc, char *argv[]) {
// Handle signals for graceful exit
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
RGBMatrix::Options defaults;
defaults.hardware_mapping = "moduleair_pinout";
defaults.rows = 64;
defaults.cols = 128;
defaults.chain_length = 1;
defaults.parallel = 1;
defaults.row_address_type = 3;
defaults.show_refresh_rate = true;
defaults.brightness = 100;
defaults.pwm_bits = 1;
//defaults.panel_type = "FM6126A";
defaults.disable_hardware_pulsing = false;
Canvas *canvas = RGBMatrix::CreateFromFlags(&argc, &argv, &defaults);
if (canvas == NULL)
return 1;
// Display welcome screen once
draw_welcome_screen(canvas);
// Sleep briefly to allow for updates
usleep(10000000); // 10 s
// Clean up
canvas->Clear();
delete canvas;
return 0;
}

34
sqlite/read_config.py Normal file
View File

@@ -0,0 +1,34 @@
'''
____ ___ _ _ _
/ ___| / _ \| | (_) |_ ___
\___ \| | | | | | | __/ _ \
___) | |_| | |___| | || __/
|____/ \__\_\_____|_|\__\___|
Script to read data from a sqlite database
/usr/bin/python3 /var/www/moduleair_pro_4g/sqlite/read_config.py
'''
import sqlite3
import sys
# Connect to the SQLite database
conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
cursor = conn.cursor()
query = f"SELECT * FROM config_table"
cursor.execute(query)
rows = cursor.fetchall()
rows.reverse() # Reverse the order in Python (to get ascending order)
# Display the results
for row in rows:
print(row)
# Close the database connection
conn.close()

140
update_firmware.sh Normal file
View File

@@ -0,0 +1,140 @@
#!/bin/bash
# moduleair Pro 4G - Comprehensive Update Script
# This script performs a complete system update including git pull,
# config initialization, and service management
# Non-interactive version for WebUI
#to test:
# sudo chmod +x /var/www/moduleair_pro_4g/update_firmware.sh
# sudo /var/www/moduleair_pro_4g/update_firmware.sh
echo "======================================"
echo "ModuleAir Pro 4G - Firmware Update"
echo "======================================"
echo "Started at: $(date)"
echo ""
# Set working directory
cd /var/www/moduleair_pro_4g
# Ensure this script is executable
chmod +x /var/www/moduleair_pro_4g/update_firmware.sh
# 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..."
# Disable filemode to prevent permission issues
git -C /var/www/moduleair_pro_4g config core.fileMode false
check_status "Git fileMode disabled"
# Fetch latest changes
git fetch origin
check_status "Git fetch"
# Show current branch
print_status "Current branch: $(git branch --show-current)"
# Check for local changes
if [ -n "$(git status --porcelain)" ]; then
print_status "Warning: Local changes detected, stashing..."
git stash push -m "Auto-stash before update $(date)"
check_status "Git stash"
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/moduleair_pro_4g/sqlite/set_config_smart.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/moduleair_pro_4g/update_firmware.sh
sudo chmod 755 /var/www/moduleair_pro_4g/sqlite/*.py
sudo chmod 755 /var/www/moduleair_pro_4g/NPM/*.py
sudo chmod 755 /var/www/moduleair_pro_4g/BME280/*.py
sudo chmod 755 /var/www/moduleair_pro_4g/SARA/*.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=(
"moduleair-npm-data.timer"
"moduleair-sara-data.timer"
"moduleair-bme280-data.timer"
)
for service in "${services[@]}"; do
if systemctl list-unit-files | grep -q "$service"; then
# Check if service is enabled before restarting
if systemctl is-enabled --quiet "$service" 2>/dev/null; then
print_status "Restarting enabled service: $service"
sudo systemctl restart "$service"
if systemctl is-active --quiet "$service"; then
print_status "$service is running"
else
print_status "$service failed to start"
fi
else
print_status " Service $service is disabled, skipping restart"
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/moduleair_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/moduleair_pro_4g/logs -name "*.log" -size +10M -exec truncate -s 0 {} \;
check_status "Log cleanup"
print_status ""
print_status "======================================"
print_status "Update completed successfully!"
print_status "======================================"
exit 0