diff --git a/html/admin.html b/html/admin.html index 026dcd6..c4f619f 100755 --- a/html/admin.html +++ b/html/admin.html @@ -71,39 +71,41 @@ -
- - -
- +
- +
- + +
+ + +
+ +
@@ -158,6 +160,44 @@ + +
+
+

SystemD Services

+
+
+ Service Status + +
+
+ + + + + + + + + + + + + + + + +
ServiceDescriptionFrequencyStatusEnabledActions
Loading services...
+
+
+
+
+ @@ -218,126 +258,111 @@ window.onload = function() { -//NEW way to get config (SQLite) -$.ajax({ - url: 'launcher.php?type=get_config_sqlite', - dataType:'json', - //dataType: 'json', // Specify that you expect a JSON response - method: 'GET', // Use GET or POST depending on your needs - success: function(response) { - console.log("Getting SQLite config table:"); - console.log(response); - //device name - const deviceName = document.getElementById("device_name"); - deviceName.value = response.deviceName; - //device name_side bar - const elements = document.querySelectorAll('.sideBar_sensorName'); - elements.forEach((element) => { - element.innerText = response.deviceName; - }); - //device ID - const deviceID = response.deviceID.trim().toUpperCase(); - const device_ID = document.getElementById("device_ID"); - device_ID.value = response.deviceID.toUpperCase(); - //nextPM send 5 channels - const checkbox_nmp5channels = document.getElementById("check_NPM_5channels"); - checkbox_nmp5channels.checked = response.npm_5channel; - - - - - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - });//end AJAX - - //getting config_scripts table + //NEW way to get config (SQLite) $.ajax({ - url: 'launcher.php?type=get_config_scripts_sqlite', - dataType:'json', - //dataType: 'json', // Specify that you expect a JSON response - method: 'GET', // Use GET or POST depending on your needs - success: function(response) { - console.log("Getting SQLite config scripts table:"); - console.log(response); + url: 'launcher.php?type=get_config_sqlite', + dataType:'json', + //dataType: 'json', // Specify that you expect a JSON response + method: 'GET', // Use GET or POST depending on your needs + success: function(response) { + console.log("Getting SQLite config table:"); + console.log(response); + //device name + const deviceName = document.getElementById("device_name"); + deviceName.value = response.deviceName; + //device name_side bar + const elements = document.querySelectorAll('.sideBar_sensorName'); + elements.forEach((element) => { + element.innerText = response.deviceName; + }); + //device ID + const deviceID = response.deviceID.trim().toUpperCase(); + const device_ID = document.getElementById("device_ID"); + device_ID.value = response.deviceID.toUpperCase(); - const checkbox_NPM = document.getElementById("check_NPM"); - const checkbox_bme = document.getElementById("check_bme280"); - const checkbox_CO2 = document.getElementById("check_CO2"); - const checkbox_SFA30 = document.getElementById("check_sensirionSFA30"); - - checkbox_NPM.checked = response["NPM/get_data_modbus_v3.py"]; - checkbox_bme.checked = response["BME280/get_data_v2.py"]; - checkbox_SFA30.checked = response["sensirion/SFA30_read.py"]; - checkbox_CO2.checked = response["MH-Z19/write_data.py"]; - - //si sonde envea is true - if (response["envea/read_value_v2.py"]) { - add_sondeEnveaContainer(); + const checkbox_nmp5channels = document.getElementById("check_NPM_5channels"); - } - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - });//end AJAX + const checkbox_bme = document.getElementById("check_bme280"); + const checkbox_CO2 = document.getElementById("check_CO2"); + const checkbox_SFA30 = document.getElementById("check_sensirionSFA30"); -//get system time and RTC module -$.ajax({ - url: 'launcher.php?type=sys_RTC_module_time', - dataType: 'json', // Specify that you expect a JSON response - method: 'GET', // Use GET or POST depending on your needs - success: function(response) { - console.log(response); - // Update the input fields with the received JSON data - document.getElementById("sys_local_time").value = response.system_local_time; - document.getElementById("sys_UTC_time").value = response.system_utc_time; - document.getElementById("RTC_utc_time").value = response.rtc_module_time; + checkbox_nmp5channels.checked = response.npm_5channel; + checkbox_bme.checked = response["BME280"]; + checkbox_SFA30.checked = response["SFA30"]; + checkbox_CO2.checked = response["MH-Z19"]; - // Get the time difference - const timeDiff = response.time_difference_seconds; + //si sonde envea is true + if (response["envea"]) { + add_sondeEnveaContainer(); + } - // Reference to the alert container - const alertContainer = document.getElementById("alert_container"); - // Remove any previous alert - alertContainer.innerHTML = ""; + }, + error: function(xhr, status, error) { + console.error('AJAX request failed:', status, error); + } + });//end AJAX + + - // Add an alert based on time difference - if (typeof timeDiff === "number") { - if (timeDiff >= 0 && timeDiff <= 10) { - alertContainer.innerHTML = ` - `; - } else if (timeDiff > 10) { - alertContainer.innerHTML = ` - `; - } - } - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - });//end ajax + //get system time and RTC module + $.ajax({ + url: 'launcher.php?type=sys_RTC_module_time', + dataType: 'json', // Specify that you expect a JSON response + method: 'GET', // Use GET or POST depending on your needs + success: function(response) { + console.log(response); + // Update the input fields with the received JSON data + document.getElementById("sys_local_time").value = response.system_local_time; + document.getElementById("sys_UTC_time").value = response.system_utc_time; + document.getElementById("RTC_utc_time").value = response.rtc_module_time; -//get local RTC -$.ajax({ - url: 'launcher.php?type=RTC_time', - dataType: 'text', // Specify that you expect a JSON response - method: 'GET', // Use GET or POST depending on your needs - success: function(response) { - console.log("Local RTC: " + response); - const RTC_Element = document.getElementById("RTC_time"); - RTC_Element.textContent = response; - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - });//end ajax + // Get the time difference + const timeDiff = response.time_difference_seconds; + + // Reference to the alert container + const alertContainer = document.getElementById("alert_container"); + + // Remove any previous alert + alertContainer.innerHTML = ""; + + // Add an alert based on time difference + if (typeof timeDiff === "number") { + if (timeDiff >= 0 && timeDiff <= 10) { + alertContainer.innerHTML = ` + `; + } else if (timeDiff > 10) { + alertContainer.innerHTML = ` + `; + } + } + }, + error: function(xhr, status, error) { + console.error('AJAX request failed:', status, error); + } + });//end ajax + + //get local RTC + $.ajax({ + url: 'launcher.php?type=RTC_time', + dataType: 'text', // Specify that you expect a JSON response + method: 'GET', // Use GET or POST depending on your needs + success: function(response) { + console.log("Local RTC: " + response); + const RTC_Element = document.getElementById("RTC_time"); + RTC_Element.textContent = response; + }, + error: function(xhr, status, error) { + console.error('AJAX request failed:', status, error); + } + });//end ajax + + // Load services on page load + refreshServices(); } //end windows on load @@ -391,78 +416,6 @@ function update_config_sqlite(param, value){ }); } -function update_config_scripts_sqlite(param, value) { - console.log("Updating scripts sqlite ", param, " : ", value); - const toastLiveExample = document.getElementById('liveToast') - const toastBody = toastLiveExample.querySelector('.toast-body'); - - $.ajax({ - url: 'launcher.php?type=update_config_scripts_sqlite¶m=' + param + '&value=' + value, - dataType: 'json', - method: 'GET', - cache: false, - success: function(response) { - console.log(response); - // Format the response nicely - let formattedMessage = ''; - - if (response.success) { - // Success message - toastLiveExample.classList.remove('text-bg-danger'); - toastLiveExample.classList.add('text-bg-success'); - - formattedMessage = ` - Success!
- Parameter: ${response.script_path || param}
- Value: ${response.enabled !== undefined ? response.enabled : value}
- ${response.message || ''} - `; - - if (response.script_path == "envea/read_value_v2.py") { - console.log("envea sondes activated"); - add_sondeEnveaContainer(); - - } - } else { - // Error message - toastLiveExample.classList.remove('text-bg-success'); - toastLiveExample.classList.add('text-bg-danger'); - - formattedMessage = ` - Error!
- ${response.error || 'Unknown error'}
- Parameter: ${response.script_path || param} - `; - } - - // Update the toast body with formatted content - toastBody.innerHTML = formattedMessage; - // Show the toast - const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample) - toastBootstrap.show() - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - }); -} - - -function update_config(param, value){ - console.log("Updating ",param," : ", value); - $.ajax({ - url: 'launcher.php?type=update_config¶m='+param+'&value='+value, - dataType: 'text', // Specify that you expect a JSON response - method: 'GET', // Use GET or POST depending on your needs - cache: false, // Prevent AJAX from caching - success: function(response) { - console.log(response); - }, - error: function(xhr, status, error) { - console.error('AJAX request failed:', status, error); - } - }); -} function updateGitPull(){ console.log("Updating device (git pull)"); @@ -537,6 +490,253 @@ function set_RTC_withBrowser(){ } + +/* + ____ _ __ __ _ + / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_ + \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __| + ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_ + |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__| + |___/ +*/ + +function refreshServices() { + console.log("Refreshing services status"); + + $.ajax({ + url: 'launcher.php?type=get_systemd_services', + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + console.log("Services data:", response); + + if (response.success) { + displayServices(response.services); + } else { + showServiceError("Failed to load services: " + response.error); + } + }, + error: function(xhr, status, error) { + console.error('Failed to load services:', error); + showServiceError("Failed to load services: " + error); + } + }); +} + +function displayServices(services) { + const tbody = document.getElementById('services-tbody'); + tbody.innerHTML = ''; + + services.forEach(function(service) { + const row = document.createElement('tr'); + + // Service name + const nameCell = document.createElement('td'); + nameCell.textContent = service.display_name || service.name; + row.appendChild(nameCell); + + // Description + const descCell = document.createElement('td'); + descCell.textContent = service.description || 'No description available'; + descCell.className = 'text-muted small'; + row.appendChild(descCell); + + // Frequency + const freqCell = document.createElement('td'); + freqCell.textContent = service.frequency || 'Unknown'; + freqCell.className = 'text-primary small fw-bold'; + row.appendChild(freqCell); + + // Status + const statusCell = document.createElement('td'); + const statusBadge = document.createElement('span'); + statusBadge.className = `badge ${service.active ? 'bg-success' : 'bg-danger'}`; + statusBadge.textContent = service.active ? 'Running' : 'Stopped'; + statusCell.appendChild(statusBadge); + row.appendChild(statusCell); + + // Enabled + const enabledCell = document.createElement('td'); + const enabledBadge = document.createElement('span'); + enabledBadge.className = `badge ${service.enabled ? 'bg-info' : 'bg-secondary'}`; + enabledBadge.textContent = service.enabled ? 'Enabled' : 'Disabled'; + enabledCell.appendChild(enabledBadge); + row.appendChild(enabledCell); + + // Actions + const actionsCell = document.createElement('td'); + + // Restart button + const restartBtn = document.createElement('button'); + restartBtn.className = 'btn btn-sm btn-warning me-2'; + restartBtn.innerHTML = ' Restart'; + restartBtn.onclick = function() { + restartService(service.name); + }; + actionsCell.appendChild(restartBtn); + + // Enable/Disable button + const toggleBtn = document.createElement('button'); + toggleBtn.className = `btn btn-sm ${service.enabled ? 'btn-danger' : 'btn-success'}`; + toggleBtn.innerHTML = service.enabled ? ' Disable' : ' Enable'; + toggleBtn.onclick = function() { + toggleService(service.name, !service.enabled); + }; + actionsCell.appendChild(toggleBtn); + + row.appendChild(actionsCell); + tbody.appendChild(row); + }); +} + +function showServiceError(message) { + const tbody = document.getElementById('services-tbody'); + tbody.innerHTML = ` + + + ${message} + + + `; +} + +function restartService(serviceName) { + console.log(`Restarting service: ${serviceName}`); + + if (!confirm(`Are you sure you want to restart ${serviceName}?`)) { + return; + } + + const toastLiveExample = document.getElementById('liveToast'); + const toastBody = toastLiveExample.querySelector('.toast-body'); + + $.ajax({ + url: 'launcher.php?type=restart_systemd_service&service=' + encodeURIComponent(serviceName), + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + console.log('Service restart response:', response); + + let formattedMessage = ''; + + if (response.success) { + // Success message + toastLiveExample.classList.remove('text-bg-danger'); + toastLiveExample.classList.add('text-bg-success'); + + formattedMessage = ` + Success!
+ Service: ${serviceName}
+ ${response.message || 'Service restarted successfully'} + `; + + // Refresh services after a short delay + setTimeout(refreshServices, 2000); + } else { + // Error message + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + + formattedMessage = ` + Error!
+ Service: ${serviceName}
+ ${response.error || 'Unknown error occurred'} + `; + } + + // Update and show toast + toastBody.innerHTML = formattedMessage; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + }, + error: function(xhr, status, error) { + console.error('Failed to restart service:', error); + + // Show error toast + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + toastBody.innerHTML = ` + Request Failed!
+ Service: ${serviceName}
+ Error: ${error} + `; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + } + }); +} + +function toggleService(serviceName, enable) { + const action = enable ? 'enable' : 'disable'; + console.log(`${action} service: ${serviceName}`); + + if (!confirm(`Are you sure you want to ${action} ${serviceName}?`)) { + return; + } + + const toastLiveExample = document.getElementById('liveToast'); + const toastBody = toastLiveExample.querySelector('.toast-body'); + + $.ajax({ + url: 'launcher.php?type=toggle_systemd_service&service=' + encodeURIComponent(serviceName) + '&enable=' + enable, + dataType: 'json', + method: 'GET', + cache: false, + success: function(response) { + console.log('Service toggle response:', response); + + let formattedMessage = ''; + + if (response.success) { + // Success message + toastLiveExample.classList.remove('text-bg-danger'); + toastLiveExample.classList.add('text-bg-success'); + + formattedMessage = ` + Success!
+ Service: ${serviceName}
+ ${response.message || `Service ${action}d successfully`} + `; + + // Refresh services after a short delay + setTimeout(refreshServices, 2000); + } else { + // Error message + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + + formattedMessage = ` + Error!
+ Service: ${serviceName}
+ ${response.error || 'Unknown error occurred'} + `; + } + + // Update and show toast + toastBody.innerHTML = formattedMessage; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + }, + error: function(xhr, status, error) { + console.error('Failed to toggle service:', error); + + // Show error toast + toastLiveExample.classList.remove('text-bg-success'); + toastLiveExample.classList.add('text-bg-danger'); + toastBody.innerHTML = ` + Request Failed!
+ Service: ${serviceName}
+ Error: ${error} + `; + const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample); + toastBootstrap.show(); + } + }); +} + + diff --git a/html/launcher.php b/html/launcher.php index 0f7c84d..3ebcb9d 100755 --- a/html/launcher.php +++ b/html/launcher.php @@ -664,3 +664,215 @@ if ($type == "wifi_scan_old") { echo $json_data; } + + + +/* + ____ _ ____ _ __ __ _ + / ___| _ _ ___| |_ ___ _ __ ___ | _ \ / ___| ___ _ ____ _(_) ___ ___| \/ | __ _ _ __ __ _ __ _ ___ _ __ ___ ___ _ __ | |_ + \___ \| | | / __| __/ _ \ '_ ` _ \| | | | \___ \ / _ \ '__\ \ / / |/ __/ _ \ |\/| |/ _` | '_ \ / _` |/ _` |/ _ \ '_ ` _ \ / _ \ '_ \| __| + ___) | |_| \__ \ || __/ | | | | | |_| | ___) | __/ | \ V /| | (_| __/ | | | (_| | | | | (_| | (_| | __/ | | | | | __/ | | | |_ + |____/ \__, |___/\__\___|_| |_| |_|____/ |____/ \___|_| \_/ |_|\___\___|_| |_|\__,_|_| |_|\__,_|\__, |\___|_| |_| |_|\___|_| |_|\__| + |___/ |___/ +*/ + +// Get systemd services status +if ($type == "get_systemd_services") { + try { + // List of NebuleAir services to monitor with descriptions and frequencies + $services = [ + 'moduleair-npm-data.timer' => [ + 'description' => 'Collects particulate matter data from NextPM sensor', + 'frequency' => 'Every 10 seconds' + ], + 'moduleair-co2-data.timer' => [ + 'description' => 'Reads environmental data from Envea sensors', + 'frequency' => 'Every 10 seconds' + ], + 'moduleair-sara-data.timer' => [ + 'description' => 'Transmits collected data via 4G cellular modem', + 'frequency' => 'Every 60 seconds' + ], + 'moduleair-bme280-data.timer' => [ + 'description' => 'Monitors temperature and humidity (BME280)', + 'frequency' => 'Every 2 minutes' + ], + 'moduleair-db-cleanup-data.timer' => [ + 'description' => 'Cleans up old data from database', + 'frequency' => 'Daily' + ] + ]; + + $serviceStatus = []; + + foreach ($services as $service => $serviceInfo) { + // Get service active status + $activeCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null"; + $activeStatus = trim(shell_exec($activeCmd)); + $isActive = ($activeStatus === 'active'); + + // Get service enabled status + $enabledCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null"; + $enabledStatus = trim(shell_exec($enabledCmd)); + $isEnabled = ($enabledStatus === 'enabled'); + + // Clean up service name for display + $displayName = str_replace(['.timer', 'nebuleair-', '-data'], '', $service); + $displayName = ucfirst(str_replace('-', ' ', $displayName)); + + $serviceStatus[] = [ + 'name' => $service, + 'display_name' => $displayName, + 'description' => $serviceInfo['description'], + 'frequency' => $serviceInfo['frequency'], + 'active' => $isActive, + 'enabled' => $isEnabled + ]; + } + + echo json_encode([ + 'success' => true, + 'services' => $serviceStatus + ], JSON_PRETTY_PRINT); + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } +} + +// Restart a systemd service +if ($type == "restart_systemd_service") { + $service = $_GET['service'] ?? null; + + if (empty($service)) { + echo json_encode([ + 'success' => false, + 'error' => 'No service specified' + ]); + exit; + } + + // Validate service name (security check) + $allowedServices = [ + 'moduleair-npm-data.timer', + 'moduleair-sara-data.timer', + 'nebuleair-bme280-data.timer', + 'moduleair-co2-data.timer', + 'moduleair-db-cleanup-data.timer' + ]; + + if (!in_array($service, $allowedServices)) { + echo json_encode([ + 'success' => false, + 'error' => 'Invalid service name' + ]); + exit; + } + + try { + // Restart the service + $command = "sudo systemctl restart " . escapeshellarg($service) . " 2>&1"; + $output = shell_exec($command); + + // Check if restart was successful + $statusCmd = "systemctl is-active " . escapeshellarg($service) . " 2>/dev/null"; + $status = trim(shell_exec($statusCmd)); + + if ($status === 'active' || empty($output)) { + echo json_encode([ + 'success' => true, + 'message' => "Service $service restarted successfully" + ]); + } else { + echo json_encode([ + 'success' => false, + 'error' => "Failed to restart service: $output" + ]); + } + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } +} + +// Enable/disable a systemd service +if ($type == "toggle_systemd_service") { + $service = $_GET['service'] ?? null; + $enable = $_GET['enable'] ?? null; + + if (empty($service) || $enable === null) { + echo json_encode([ + 'success' => false, + 'error' => 'Missing service name or enable parameter' + ]); + exit; + } + + // Validate service name (security check) + $allowedServices = [ + 'moduleair-npm-data.timer', + 'moduleair-sara-data.timer', + 'nebuleair-bme280-data.timer', + 'moduleair-co2-data.timer', + 'moduleair-db-cleanup-data.timer' + ]; + + if (!in_array($service, $allowedServices)) { + echo json_encode([ + 'success' => false, + 'error' => 'Invalid service name' + ]); + exit; + } + + try { + $enable = filter_var($enable, FILTER_VALIDATE_BOOLEAN); + $action = $enable ? 'enable' : 'disable'; + + // Enable/disable the service + $command = "sudo systemctl $action " . escapeshellarg($service) . " 2>&1"; + $output = shell_exec($command); + + // If disabling, also stop the service + if (!$enable) { + $stopCommand = "sudo systemctl stop " . escapeshellarg($service) . " 2>&1"; + $stopOutput = shell_exec($stopCommand); + } + + // If enabling, also start the service + if ($enable) { + $startCommand = "sudo systemctl start " . escapeshellarg($service) . " 2>&1"; + $startOutput = shell_exec($startCommand); + } + + // Check if the operation was successful + $statusCmd = "systemctl is-enabled " . escapeshellarg($service) . " 2>/dev/null"; + $status = trim(shell_exec($statusCmd)); + + $expectedStatus = $enable ? 'enabled' : 'disabled'; + + if ($status === $expectedStatus) { + echo json_encode([ + 'success' => true, + 'message' => "Service $service " . ($enable ? 'enabled and started' : 'disabled and stopped') . " successfully" + ]); + } else { + echo json_encode([ + 'success' => false, + 'error' => "Failed to $action service: $output" + ]); + } + + } catch (Exception $e) { + echo json_encode([ + 'success' => false, + 'error' => $e->getMessage() + ]); + } +} diff --git a/loop/SARA_send_data_v2.py b/loop/SARA_send_data_v2.py index ac00d35..0d1700d 100755 --- a/loop/SARA_send_data_v2.py +++ b/loop/SARA_send_data_v2.py @@ -105,15 +105,6 @@ from datetime import datetime # Record the start time of the script start_time_script = time.time() -# Check system uptime -with open('/proc/uptime', 'r') as f: - uptime_seconds = float(f.readline().split()[0]) - -# Skip execution if uptime is less than 2 minutes (120 seconds) -if uptime_seconds < 120: - print(f"System just booted ({uptime_seconds:.2f} seconds uptime), skipping execution.") - sys.exit() - #Payload CSV to be sent to data.moduleair.fr payload_csv = [None] * 25 #Payload JSON to be sent to uSpot @@ -131,6 +122,50 @@ uSpot_profile_id = 1 conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db") cursor = conn.cursor() +def update_config_sqlite(key, value, value_type=None): + """ + Update or insert a configuration value in SQLite config table + + Args: + key (str): Configuration key + value: Configuration value (any type) + value_type (str, optional): Force specific type ('str', 'int', 'float', 'bool') + If None, auto-detect from value type + + Returns: + bool: True if successful, False otherwise + """ + try: + # Auto-detect type if not specified + if value_type is None: + if isinstance(value, bool): + value_type = 'bool' + elif isinstance(value, int): + value_type = 'int' + elif isinstance(value, float): + value_type = 'float' + else: + value_type = 'str' + + # Convert value to string for storage + if value_type == 'bool': + str_value = '1' if value else '0' + else: + str_value = str(value) + + # Use INSERT OR REPLACE to update existing or create new + cursor.execute(""" + INSERT OR REPLACE INTO config_table (key, value, type) + VALUES (?, ?, ?) + """, (key, str_value, value_type)) + + conn.commit() + print(f"✓ Config updated: {key} = {value} ({value_type})") + return True + + except Exception as e: + print(f"✗ Error updating config {key}: {e}") + return False #get config data from SQLite table def load_config_sqlite(): @@ -165,32 +200,7 @@ def load_config_sqlite(): print(f"Error loading config from SQLite: {e}") return {} -def load_config_scripts_sqlite(): - """ - Load script configuration data from SQLite config_scripts_table - - Returns: - dict: Script paths as keys and enabled status as boolean values - """ - try: - # Query the config_scripts_table - cursor.execute("SELECT script_path, enabled FROM config_scripts_table") - rows = cursor.fetchall() - - # Create config dictionary with script paths as keys and enabled status as boolean values - scripts_config = {} - for script_path, enabled in rows: - # Convert integer enabled value (0/1) to boolean - scripts_config[script_path] = bool(enabled) - - return scripts_config - - except Exception as e: - print(f"Error loading scripts config from SQLite: {e}") - return {} -# Define the config file path -config_file = '/var/www/moduleair_pro_4g/config.json' #Load config config = load_config_sqlite() @@ -205,16 +215,12 @@ Sara_baudrate = config.get('SaraR4_baudrate', 115200) npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM selected_networkID = int(config.get('SARA_R4_neworkID', 0)) send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot () +bme_280_config = config.get('BME280', False) +co2_mhz19= config.get('MH-Z19', False) +sensirion_sfa30= config.get('SFA30', False) reset_uSpot_url = False -#config_scripts -config_scripts = load_config_scripts_sqlite() -bme_280_config = config_scripts.get('BME280/get_data_v2.py', False) -envea_cairsens= config_scripts.get('envea/read_value_v2.py', False) -co2_mhz19= config_scripts.get('MH-Z19/write_data.py', False) -sensirion_sfa30= config_scripts.get('sensirion/SFA30_read.py', False) - #update device id in the payload json payload_json["moduleairid"] = device_id @@ -461,6 +467,16 @@ try: ''' print('

START LOOP

') + # Check system uptime + with open('/proc/uptime', 'r') as f: + uptime_seconds = float(f.readline().split()[0]) + + # Skip execution if uptime is less than 2 minutes (120 seconds) + if uptime_seconds < 120: + print(f"System just booted ({uptime_seconds:.2f} seconds uptime), skipping execution.") + update_config_sqlite('SARA_network_status', 'booting') + sys.exit() + #Local timestamp cursor.execute("SELECT * FROM timestamp_table LIMIT 1") row = cursor.fetchone() # Get the first (and only) row @@ -561,34 +577,7 @@ try: else: print("No data available in the database.") - #envea - if envea_cairsens: - print("➡️Getting envea cairsens values") - cursor.execute("SELECT * FROM data_envea ORDER BY timestamp DESC LIMIT 6") - rows = cursor.fetchall() - # Exclude the timestamp column (assuming first column is timestamp) - data_values = [row[1:] for row in rows] # Exclude timestamp - # Compute column-wise average, ignoring 0 values - averages = [] - for col in zip(*data_values): # Iterate column-wise - filtered_values = [val for val in col if val != 0] # Remove zeros - if filtered_values: - avg = round(sum(filtered_values) / len(filtered_values)) # Compute average - else: - avg = 0 # If all values were zero, store 0 - averages.append(avg) - - # Store averages in specific indices - payload_csv[9] = averages[0] # envea_no2 - payload_csv[10] = averages[1] # envea_h2s - payload_csv[11] = averages[2] # envea_nh3 - - #Add data to payload JSON - payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[0])}) - payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NO2", "value": str(averages[1])}) - payload_json["sensordatavalues"].append({"value_type": "CAIRSENS_NH3", "value": str(averages[2])}) - - + #print("Verify SARA R4 connection") # Getting the LTE Signal @@ -662,6 +651,9 @@ try: # 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('⚠️ATTENTION: Signal Quality indicates no signal (99)⚠️') print("TRY TO RECONNECT:") command = f'AT+COPS=1,2,"{selected_networkID}"\r' @@ -678,6 +670,8 @@ try: sys.exit() else: print("Signal Quality:", signal_quality) + update_config_sqlite('SARA_signal_quality', signal_quality) + ''' @@ -776,7 +770,8 @@ try: print("*****") print('ATTENTION: HTTP operation failed') print("*****") - + update_config_sqlite('SARA_network_status', 'error') + # Get error code print("Getting error code") @@ -838,6 +833,8 @@ try: # Si la commande HTTP a réussi print('✅✅HTTP operation successful.') + #update SARA_network_status + update_config_sqlite('SARA_network_status', 'connected') #4. Read reply from server print("Reply from server:") diff --git a/matrix/screenNetwork/network_status b/matrix/screenNetwork/network_status new file mode 100644 index 0000000..a1b8096 Binary files /dev/null and b/matrix/screenNetwork/network_status differ diff --git a/matrix/screenNetwork/network_status.cc b/matrix/screenNetwork/network_status.cc new file mode 100644 index 0000000..5523956 --- /dev/null +++ b/matrix/screenNetwork/network_status.cc @@ -0,0 +1,489 @@ +/* + _ _ _____ _______ _____ ____ _ __ + | \ | | ____|_ _\ \ / / _ \| _ \| |/ / + | \| | _| | | \ \ /\ / / | | | |_) | ' / + | |\ | |___ | | \ V V /| |_| | _ <| . \ + |_| \_|_____| |_| \_/\_/ \___/|_| \_\_|\_\ + + +Modern 4G Connection Status Display +Get 4G status and signal quality from SQLite database config_table + +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/screenNetwork/network_status.cc -o /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status -lrgbmatrix -lsqlite3 + +Pour lancer: +sudo /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status +*/ + +#include "led-matrix.h" +#include "graphics.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using rgb_matrix::RGBMatrix; +using rgb_matrix::Canvas; + +std::atomic running(true); + +// Define color codes for console output +#define RESET "\033[0m" +#define RED "\033[31m" +#define GREEN "\033[32m" +#define YELLOW "\033[33m" +#define BLUE "\033[34m" +#define MAGENTA "\033[35m" +#define CYAN "\033[36m" +#define WHITE "\033[37m" + +// Path to the SQLite database +const std::string DB_PATH = "/var/www/moduleair_pro_4g/sqlite/sensors.db"; + +// Animation counter for connection indicators +int animation_frame = 0; + +void signal_handler(int signum) { + running = false; +} + +void log(const std::string& type, const std::string& message) { + std::string color; + + if (type == "BLUE") color = BLUE; + else if (type == "GREEN") color = GREEN; + else if (type == "YELLOW") color = YELLOW; + else if (type == "RED") color = RED; + else color = WHITE; + + std::cout << color << message << RESET << std::endl; +} + +// Function to get 4G status from database +bool get_4g_status(sqlite3* db, std::string& network_status, int& signal_quality) { + sqlite3_stmt* stmt; + bool status_found = false; + bool signal_found = false; + + log("BLUE", "Querying 4G status from database..."); + + // Get network status + const char* status_query = "SELECT value FROM config_table WHERE key='SARA_network_status'"; + if (sqlite3_prepare_v2(db, status_query, -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + network_status = (char*)sqlite3_column_text(stmt, 0); + status_found = true; + std::cout << " Network Status: " << network_status << std::endl; + } else { + std::cout << " No network status found in config_table" << std::endl; + network_status = "unknown"; + } + sqlite3_finalize(stmt); + } + + // Get signal quality + const char* signal_query = "SELECT value FROM config_table WHERE key='SARA_signal_quality'"; + if (sqlite3_prepare_v2(db, signal_query, -1, &stmt, NULL) == SQLITE_OK) { + if (sqlite3_step(stmt) == SQLITE_ROW) { + signal_quality = sqlite3_column_int(stmt, 0); + signal_found = true; + std::cout << " Signal Quality: " << signal_quality << " dBm" << std::endl; + } else { + std::cout << " No signal quality found in config_table" << std::endl; + signal_quality = -999; + } + sqlite3_finalize(stmt); + } + + return status_found || signal_found; +} + +// Function to draw signal strength bars +void draw_signal_bars(Canvas* canvas, int x, int y, int signal_rssi) { + rgb_matrix::Color bar_color; + rgb_matrix::Color bg_color(0, 0, 0); + + // Convert RSSI to signal strength (0-5 bars) + // RSSI scale: 0-31 (where 31 is strongest) + int bars = 0; + if (signal_rssi >= 20) bars = 5; // Excellent (20-31) + else if (signal_rssi >= 15) bars = 4; // Good (15-19) + else if (signal_rssi >= 10) bars = 3; // Fair (10-14) + else if (signal_rssi >= 5) bars = 2; // Poor (5-9) + else if (signal_rssi >= 1) bars = 1; // Very Poor (1-4) + else bars = 0; // No signal (0) + + // Choose color based on signal strength + if (bars >= 4) bar_color = rgb_matrix::Color(0, 255, 0); // Green - Excellent/Good + else if (bars >= 2) bar_color = rgb_matrix::Color(255, 255, 0); // Yellow - Fair/Poor + else if (bars >= 1) bar_color = rgb_matrix::Color(255, 165, 0); // Orange - Very Poor + else bar_color = rgb_matrix::Color(255, 0, 0); // Red - No signal + + // Draw 5 signal bars of increasing height + for (int i = 0; i < 5; i++) { + int bar_height = (i + 1) * 2; // Heights: 2, 4, 6, 8, 10 + int bar_x = x + i * 3; + + // Clear the bar area first + for (int bx = 0; bx < 2; bx++) { + for (int by = 0; by < 10; by++) { + canvas->SetPixel(bar_x + bx, y - by, 0, 0, 0); + } + } + + // Draw the bar if it should be active + if (i < bars) { + for (int bx = 0; bx < 2; bx++) { + for (int by = 0; by < bar_height; by++) { + canvas->SetPixel(bar_x + bx, y - by, bar_color.r, bar_color.g, bar_color.b); + } + } + } + } +} + +// Function to draw connection status icon +void draw_connection_icon(Canvas* canvas, int x, int y, const std::string& status) { + rgb_matrix::Color icon_color; + + // Clear the icon area (20x20 pixels) + for (int ix = 0; ix < 20; ix++) { + for (int iy = 0; iy < 20; iy++) { + canvas->SetPixel(x + ix, y + iy, 0, 0, 0); + } + } + + if (status == "connected") { + icon_color = rgb_matrix::Color(0, 255, 0); // Green + // Draw a checkmark + // Simplified checkmark pattern + for (int i = 0; i < 6; i++) { + canvas->SetPixel(x + 5 + i, y + 10 + i/2, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(x + 11 + i, y + 13 - i, icon_color.r, icon_color.g, icon_color.b); + } + } else if (status == "connecting") { + // Animated connecting indicator + icon_color = rgb_matrix::Color(255, 255, 0); // Yellow + int frame = animation_frame % 8; + + // Draw rotating dots + for (int i = 0; i < 8; i++) { + if (i <= frame) { + int dot_x = x + 10 + (int)(8 * cos(i * M_PI / 4)); + int dot_y = y + 10 + (int)(8 * sin(i * M_PI / 4)); + canvas->SetPixel(dot_x, dot_y, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(dot_x+1, dot_y, icon_color.r, icon_color.g, icon_color.b); + } + } + } else if (status == "booting") { + icon_color = rgb_matrix::Color(255, 165, 0); // Orange + // Draw a simple but visible rotating indicator for booting + int frame = animation_frame % 4; // Faster rotation (4 states instead of 12) + + // Draw a rotating cross/plus pattern + int center_x = x + 10; + int center_y = y + 10; + + // Clear area first + for (int cx = -8; cx <= 8; cx++) { + for (int cy = -8; cy <= 8; cy++) { + if (center_x + cx >= 0 && center_y + cy >= 0) { + canvas->SetPixel(center_x + cx, center_y + cy, 0, 0, 0); + } + } + } + + // Draw rotating lines based on frame + if (frame == 0 || frame == 2) { + // Vertical line + for (int i = -6; i <= 6; i++) { + canvas->SetPixel(center_x, center_y + i, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(center_x + 1, center_y + i, icon_color.r, icon_color.g, icon_color.b); + } + } + if (frame == 1 || frame == 3) { + // Horizontal line + for (int i = -6; i <= 6; i++) { + canvas->SetPixel(center_x + i, center_y, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(center_x + i, center_y + 1, icon_color.r, icon_color.g, icon_color.b); + } + } + } else if (status == "disconnected" || status == "error") { + icon_color = rgb_matrix::Color(255, 0, 0); // Red + // Draw an X + for (int i = 0; i < 12; i++) { + canvas->SetPixel(x + 4 + i, y + 4 + i, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(x + 4 + i, y + 16 - i, icon_color.r, icon_color.g, icon_color.b); + } + } else { + icon_color = rgb_matrix::Color(128, 128, 128); // Gray for unknown + // Draw a question mark + // Simplified question mark + for (int i = 0; i < 8; i++) { + canvas->SetPixel(x + 6 + i, y + 4, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(x + 14, y + 5 + i/2, icon_color.r, icon_color.g, icon_color.b); + } + canvas->SetPixel(x + 10, y + 15, icon_color.r, icon_color.g, icon_color.b); + canvas->SetPixel(x + 11, y + 15, icon_color.r, icon_color.g, icon_color.b); + } +} + +// Function to draw the entire 4G status screen +void draw_4g_screen(Canvas* canvas, const std::string& network_status, int signal_quality) { + rgb_matrix::Color white(255, 255, 255); + rgb_matrix::Color cyan(0, 255, 255); + rgb_matrix::Color bg_color(0, 0, 0); + + rgb_matrix::Font title_font, value_font, small_font; + + // Load fonts - try multiple font paths + bool fonts_loaded = false; + + // Try the fonts from your working sensor display + if (title_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf") && + value_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf") && + small_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf")) { + fonts_loaded = true; + std::cout << "Loaded fonts successfully (6x9)" << std::endl; + } + // Fallback: try alternative font names + else if (title_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf") && + value_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf") && + small_font.LoadFont("/var/www/moduleair_pro_4g/matrix/fonts/4x6.bdf")) { + fonts_loaded = true; + std::cout << "Loaded fonts successfully (mixed)" << std::endl; + } + + if (!fonts_loaded) { + std::cerr << "Error loading fonts! Check font paths:" << std::endl; + std::cerr << " Looking for: /var/www/moduleair_pro_4g/matrix/fonts/6x9.bdf" << std::endl; + std::cerr << " Looking for: /var/www/moduleair_pro_4g/matrix/fonts/9x18B.bdf" << std::endl; + std::cerr << " Looking for: /var/www/moduleair_pro_4g/matrix/fonts/4x6.bdf" << std::endl; + + // Use a simple text-based display instead + rgb_matrix::Color white(255, 255, 255); + rgb_matrix::Color green(0, 255, 0); + rgb_matrix::Color red(255, 0, 0); + rgb_matrix::Color bg_color(0, 0, 0); + + // Draw basic status without fonts + canvas->Clear(); + + // Draw simple status indicators using pixels + if (network_status == "connected") { + // Draw green rectangle + for (int x = 10; x < 30; x++) { + for (int y = 10; y < 20; y++) { + canvas->SetPixel(x, y, 0, 255, 0); + } + } + } else { + // Draw red rectangle + for (int x = 10; x < 30; x++) { + for (int y = 10; y < 20; y++) { + canvas->SetPixel(x, y, 255, 0, 0); + } + } + } + + // Draw signal bars anyway + draw_signal_bars(canvas, 55, 30, signal_quality); + + std::cout << "Using fallback display (no fonts)" << std::endl; + return; + } + + // Clear screen + canvas->Clear(); + + // Draw title + rgb_matrix::DrawText(canvas, title_font, 2, title_font.baseline() + 2, cyan, &bg_color, "4G CONNECTION", 0); + + // Draw connection status section + int status_y = 25; + rgb_matrix::DrawText(canvas, value_font, 2, status_y, white, &bg_color, "Status:", 0); + + // Draw connection icon + draw_connection_icon(canvas, 40, status_y - 15, network_status); + + // Draw status text + rgb_matrix::Color status_color; + std::string status_text = network_status; + std::transform(status_text.begin(), status_text.end(), status_text.begin(), ::toupper); + + if (network_status == "connected") { + status_color = rgb_matrix::Color(0, 255, 0); // Green + } else if (network_status == "connecting") { + status_color = rgb_matrix::Color(255, 255, 0); // Yellow + } else if (network_status == "booting") { + status_color = rgb_matrix::Color(255, 165, 0); // Orange + status_text = "BOOTING"; + } else if (network_status == "disconnected" || network_status == "error") { + status_color = rgb_matrix::Color(255, 0, 0); // Red + } else { + status_color = rgb_matrix::Color(128, 128, 128); // Gray + status_text = "UNKNOWN"; + } + + rgb_matrix::DrawText(canvas, value_font, 65, status_y, status_color, &bg_color, status_text.c_str(), 0); + + // Only show signal section if NOT in booting mode + if (network_status != "booting") { + // Draw signal strength section + int signal_y = 45; + rgb_matrix::DrawText(canvas, value_font, 2, signal_y, white, &bg_color, "Signal:", 0); + + // Draw signal bars + draw_signal_bars(canvas, 45, signal_y - 2, signal_quality); + + // Draw signal value + if (signal_quality != -999) { + std::stringstream ss; + ss << "RSSI: " << signal_quality; + rgb_matrix::DrawText(canvas, small_font, 70, signal_y - 3, white, &bg_color, ss.str().c_str(), 0); + + // Draw signal quality text based on RSSI + std::string quality_text; + rgb_matrix::Color quality_color; + + if (signal_quality >= 20) { + quality_text = "EXCELLENT"; + quality_color = rgb_matrix::Color(0, 255, 0); + } else if (signal_quality >= 15) { + quality_text = "GOOD"; + quality_color = rgb_matrix::Color(0, 255, 0); + } else if (signal_quality >= 10) { + quality_text = "FAIR"; + quality_color = rgb_matrix::Color(255, 255, 0); + } else if (signal_quality >= 5) { + quality_text = "POOR"; + quality_color = rgb_matrix::Color(255, 165, 0); + } else if (signal_quality >= 1) { + quality_text = "VERY POOR"; + quality_color = rgb_matrix::Color(255, 0, 0); + } else { + quality_text = "NO SIGNAL"; + quality_color = rgb_matrix::Color(255, 0, 0); + } + + // Position quality text higher to avoid clipping + rgb_matrix::DrawText(canvas, small_font, 70, signal_y + 5, quality_color, &bg_color, quality_text.c_str(), 0); + } else { + rgb_matrix::DrawText(canvas, small_font, 70, signal_y - 3, rgb_matrix::Color(128, 128, 128), &bg_color, "NO DATA", 0); + } + } else { + // Clear the signal area completely when booting + for (int clear_x = 0; clear_x < 128; clear_x++) { + for (int clear_y = 35; clear_y < 55; clear_y++) { + canvas->SetPixel(clear_x, clear_y, 0, 0, 0); + } + } + + // Show booting message instead of signal info + int boot_msg_y = 45; + rgb_matrix::DrawText(canvas, value_font, 15, boot_msg_y, rgb_matrix::Color(255, 165, 0), &bg_color, "Initializing modem...", 0); + } + + // Draw timestamp + auto now = std::chrono::system_clock::now(); + auto time_t = std::chrono::system_clock::to_time_t(now); + auto tm = *std::localtime(&time_t); + + std::stringstream time_ss; + time_ss << std::put_time(&tm, "%H:%M:%S"); + + rgb_matrix::DrawText(canvas, small_font, 2, 60, rgb_matrix::Color(100, 100, 100), &bg_color, + ("Updated: " + time_ss.str()).c_str(), 0); + + animation_frame++; +} + +int main(int argc, char *argv[]) { + log("BLUE", "4G Status Display started"); + + // Handle signals for graceful exit + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + + // Initialize database connection + sqlite3* db; + std::cout << "Opening SQLite database at " << DB_PATH << std::endl; + int rc = sqlite3_open(DB_PATH.c_str(), &db); + if (rc) { + std::cerr << "Error opening SQLite database: " << sqlite3_errmsg(db) << std::endl; + sqlite3_close(db); + return 1; + } + + // Initialize LED matrix + log("BLUE", "Initializing LED matrix..."); + RGBMatrix::Options defaults; + defaults.hardware_mapping = "moduleair_pinout"; + defaults.rows = 64; + defaults.cols = 128; + defaults.chain_length = 1; + defaults.parallel = 1; + defaults.row_address_type = 3; + defaults.show_refresh_rate = false; + defaults.brightness = 100; + defaults.pwm_bits = 1; + defaults.panel_type = "FM6126A"; + defaults.disable_hardware_pulsing = false; + + rgb_matrix::RuntimeOptions runtime_opt; + runtime_opt.gpio_slowdown = 4; + runtime_opt.daemon = 0; + runtime_opt.drop_privileges = 0; + + Canvas *canvas = RGBMatrix::CreateFromOptions(defaults, runtime_opt); + if (canvas == NULL) { + std::cerr << "Error creating LED matrix canvas" << std::endl; + sqlite3_close(db); + return 1; + } + log("GREEN", "LED matrix initialized successfully"); + + // Main loop + log("BLUE", "Starting 4G status display loop"); + while (running) { + std::string network_status; + int signal_quality; + + // Get 4G status from database + bool data_success = get_4g_status(db, network_status, signal_quality); + + if (!data_success) { + std::cerr << "Error retrieving 4G data from database" << std::endl; + network_status = "error"; + signal_quality = -999; + } + + // Draw the screen + draw_4g_screen(canvas, network_status, signal_quality); + + // Sleep before next update + std::cout << "Update complete, sleeping for 5 seconds..." << std::endl; + for (int i = 0; i < 5 && running; i++) { + sleep(1); // Sleep in 1-second increments for responsive exit + } + } + + // Clean up + std::cout << "Program terminating, cleaning up..." << std::endl; + canvas->Clear(); + delete canvas; + sqlite3_close(db); + std::cout << "4G Status Display terminated" << std::endl; + return 0; +} \ No newline at end of file diff --git a/services/matrix_boot.sh b/services/matrix_boot.sh new file mode 100644 index 0000000..ddca6a3 --- /dev/null +++ b/services/matrix_boot.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Simple boot script - run animation then sensor display +# sudo chmod +x /var/www/moduleair_pro_4g/services/matrix_boot.sh +# sudo /var/www/moduleair_pro_4g/services/matrix_boot.sh + +echo "$(date) - Starting boot animation..." +sudo /var/www/moduleair_pro_4g/matrix/imageScreen/image_split_boot /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png + +#0echo "$(date) - Boot animation done, starting sensor display..." +#sudo /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2 + +echo "$(date) - Boot animation done, starting network display..." +sudo /var/www/moduleair_pro_4g/matrix/screenNetwork/network_status + +echo "$(date) - Both programs started" \ No newline at end of file diff --git a/services/setup_matrix_services.sh b/services/old/setup_matrix_services.sh similarity index 100% rename from services/setup_matrix_services.sh rename to services/old/setup_matrix_services.sh diff --git a/services/setup_services.sh b/services/setup_services.sh index f0d9d1a..72f54dd 100644 --- a/services/setup_services.sh +++ b/services/setup_services.sh @@ -206,52 +206,46 @@ WantedBy=timers.target EOL # Create service and timer files for LED Matrix Display -cat > /etc/systemd/system/moduleair-matrix-display.service << 'EOL' +cat > /etc/systemd/system/moduleair-boot.service << 'EOL' [Unit] -Description=moduleair LED Matrix Display Service +Description=ModuleAir Matrix Boot Display After=network.target +After=multi-user.target [Service] -Type=oneshot -ExecStart=/var/www/moduleair_pro_4g/matrix/imageScreen/image_split /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png +Type=simple +ExecStart=/var/www/moduleair_pro_4g/services/matrix_boot.sh User=root -WorkingDirectory=/var/www/moduleair_pro_4g/matrix/imageScreen/ -StandardOutput=append:/var/www/moduleair_pro_4g/logs/matrix_service.log -StandardError=append:/var/www/moduleair_pro_4g/logs/matrix_service_errors.log +WorkingDirectory=/var/www/moduleair_pro_4g/ +StandardOutput=append:/var/www/moduleair_pro_4g/logs/matrix_boot.log +StandardError=append:/var/www/moduleair_pro_4g/logs/matrix_boot.log +TimeoutStartSec=120s +RemainAfterExit=yes [Install] WantedBy=multi-user.target EOL -cat > /etc/systemd/system/moduleair-matrix-display.timer << 'EOL' -[Unit] -Description=Run moduleair LED Matrix Display every 10 seconds -Requires=moduleair-matrix-display.service - -[Timer] -OnBootSec=30s -OnUnitActiveSec=10s -AccuracySec=1s - -[Install] -WantedBy=timers.target -EOL - # Make sure the matrix executable has proper permissions echo "Setting permissions for LED matrix executable..." chmod +x /var/www/moduleair_pro_4g/matrix/imageScreen/image_split +chmod +x /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2 # Reload systemd to recognize new services systemctl daemon-reload # Enable and start all timers echo "Enabling and starting all services..." -for service in npm envea sara bme280 co2 db-cleanup matrix-display; do - systemctl enable moduleair-$service-data.timer 2>/dev/null || systemctl enable moduleair-$service.timer - systemctl start moduleair-$service-data.timer 2>/dev/null || systemctl start moduleair-$service.timer - echo "Started moduleair-$service timer" +for service in npm envea sara bme280 co2 db-cleanup; do + systemctl enable moduleair-$service-data.timer + systemctl start moduleair-$service-data.timer + echo "Started moduleair-$service-data timer" done +# Enable the boot service (runs once at boot, no timer needed) +echo "Enabling boot display service..." +systemctl enable moduleair-boot.service + echo "Checking status of all timers..." systemctl list-timers | grep moduleair diff --git a/sqlite/set_config_smart.py b/sqlite/set_config_smart.py index 505f2f4..7b26520 100644 --- a/sqlite/set_config_smart.py +++ b/sqlite/set_config_smart.py @@ -71,6 +71,10 @@ config_entries = [ ("modem_config_mode", "0", "bool"), ("deviceID", "XXXX", "str"), ("npm_5channel", "0", "bool"), + ("BME280", "0", "bool"), + ("SFA30", "0", "bool"), + ("MHZ19", "0", "bool"), + ("envea", "0", "bool"), ("latitude_raw", "0", "int"), ("longitude_raw", "0", "int"), ("latitude_precision", "0", "int"), @@ -79,10 +83,11 @@ config_entries = [ ("SaraR4_baudrate", "115200", "int"), ("NPM_solo_port", "/dev/ttyAMA5", "str"), ("sshTunnel_port", "59228", "int"), - ("SARA_R4_general_status", "connected", "str"), - ("SARA_R4_SIM_status", "connected", "str"), - ("SARA_R4_network_status", "connected", "str"), - ("SARA_R4_neworkID", "20810", "int"), + ("SARA_general_status", "connected", "str"), + ("SARA_SIM_status", "connected", "str"), + ("SARA_network_status", "connected", "str"), + ("SARA_signal_quality", "22", "int"), + ("SARA_neworkID", "20810", "int"), ("WIFI_status", "connected", "str"), ("send_uSpot", "0", "bool"), ("windMeter", "0", "bool"),