Compare commits
19 Commits
d2d881205b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc71a271eb | ||
|
|
29458c4841 | ||
|
|
3b8b51172c | ||
|
|
acdc736a38 | ||
|
|
c5f120106f | ||
|
|
93d0e3b9cf | ||
|
|
5027f9945b | ||
|
|
943243f769 | ||
|
|
5adc814e1b | ||
|
|
d481608c9a | ||
|
|
76cf2a30bf | ||
|
|
d8e004e9bd | ||
|
|
675482929e | ||
|
|
fea7c03bfb | ||
|
|
1b3cf16d55 | ||
|
|
677595835a | ||
|
|
806f95df75 | ||
|
|
96231582f5 | ||
|
|
45fa8b382a |
9
.claude/settings.local.json
Normal file
9
.claude/settings.local.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(sqlite3:*)"
|
||||
],
|
||||
"deny": []
|
||||
},
|
||||
"enableAllProjectMcpServers": false
|
||||
}
|
||||
74
BME280/get_data_v2.py
Normal file
74
BME280/get_data_v2.py
Normal 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()
|
||||
@@ -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
|
||||
|
||||
```
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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 {} \;
|
||||
|
||||
|
||||
133
html/admin.html
133
html/admin.html
@@ -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)");
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
376
installation.sh
376
installation.sh
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
BIN
matrix/welcomeScreen/welcome_screen_ICN2037
Normal file
BIN
matrix/welcomeScreen/welcome_screen_ICN2037
Normal file
Binary file not shown.
100
matrix/welcomeScreen/welcome_screen_ICN2037.cc
Normal file
100
matrix/welcomeScreen/welcome_screen_ICN2037.cc
Normal 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
34
sqlite/read_config.py
Normal 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
140
update_firmware.sh
Normal 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
|
||||
Reference in New Issue
Block a user