update
This commit is contained in:
588
html/admin.html
588
html/admin.html
@@ -71,39 +71,41 @@
|
|||||||
|
|
||||||
<!-- config_scripts_table -->
|
<!-- config_scripts_table -->
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_NPM" onchange="update_config_scripts_sqlite('NPM/get_data_modbus_v3.py', this.checked)">
|
|
||||||
<label class="form-check-label" for="check_NPM">
|
|
||||||
Next PM
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_NPM_5channels" onchange="update_config_sqlite('npm_5channel', this.checked)">
|
<input class="form-check-input" type="checkbox" value="" id="check_NPM_5channels" onchange="update_config_sqlite('npm_5channel', this.checked)">
|
||||||
<label class="form-check-label" for="check_NPM_5channels">
|
<label class="form-check-label" for="check_NPM_5channels">
|
||||||
Next PM send 5 channels (no script)
|
Send Next PM 5 channels data
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_bme280" onchange="update_config_scripts_sqlite('BME280/get_data_v2.py', this.checked)">
|
<input class="form-check-input" type="checkbox" value="" id="check_bme280" onchange="update_config_sqlite('BME280', this.checked)">
|
||||||
<label class="form-check-label" for="check_bme280">
|
<label class="form-check-label" for="check_bme280">
|
||||||
Sonde temp/hum (BME280)
|
Send temp/hum data (BME280)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_CO2" onchange="update_config_scripts_sqlite('MH-Z19/write_data.py', this.checked)">
|
<input class="form-check-input" type="checkbox" value="" id="check_CO2" onchange="update_config_sqlite('MHZ19', this.checked)">
|
||||||
<label class="form-check-label" for="check_CO2">
|
<label class="form-check-label" for="check_CO2">
|
||||||
Sonde CO2 (MH-Z19)
|
Send CO2 data (MH-Z19)
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input class="form-check-input" type="checkbox" value="" id="check_sensirionSFA30" onchange="update_config_scripts_sqlite('sensirion/SFA30_read.py', this.checked)">
|
<input class="form-check-input" type="checkbox" value="" id="check_sensirionSFA30" onchange="update_config_sqlite('SFA30', this.checked)">
|
||||||
<label class="form-check-label" for="check_sensirionSFA30">
|
<label class="form-check-label" for="check_sensirionSFA30">
|
||||||
Sonde Formaldehyde (Sensiririon SFA30)
|
Send Formaldehyde data (Sensiririon SFA30)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="check_uSpot" onchange="update_config_sqlite('send_uSpot', this.checked)" disabled>
|
||||||
|
<label class="form-check-label" for="check_uSpot">
|
||||||
|
Send to uSpot
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -158,6 +160,44 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SYSTEMD SERVICES SECTION -->
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col-lg-8 col-12">
|
||||||
|
<h4 class="mt-4">SystemD Services</h4>
|
||||||
|
<div id="services-table" class="card">
|
||||||
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
<span class="fw-bold">Service Status</span>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" onclick="refreshServices()">
|
||||||
|
<svg width="14" height="14" fill="currentColor" class="bi bi-arrow-clockwise" viewBox="0 0 16 16">
|
||||||
|
<path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
|
||||||
|
<path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
|
||||||
|
</svg>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<table class="table table-sm table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 20%">Service</th>
|
||||||
|
<th style="width: 25%">Description</th>
|
||||||
|
<th style="width: 15%">Frequency</th>
|
||||||
|
<th style="width: 10%">Status</th>
|
||||||
|
<th style="width: 10%">Enabled</th>
|
||||||
|
<th style="width: 20%">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="services-tbody">
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center py-3">Loading services...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<!-- toast -->
|
<!-- toast -->
|
||||||
|
|
||||||
@@ -218,126 +258,111 @@
|
|||||||
|
|
||||||
window.onload = function() {
|
window.onload = function() {
|
||||||
|
|
||||||
//NEW way to get config (SQLite)
|
//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
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: 'launcher.php?type=get_config_scripts_sqlite',
|
url: 'launcher.php?type=get_config_sqlite',
|
||||||
dataType:'json',
|
dataType:'json',
|
||||||
//dataType: 'json', // Specify that you expect a JSON response
|
//dataType: 'json', // Specify that you expect a JSON response
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
success: function(response) {
|
success: function(response) {
|
||||||
console.log("Getting SQLite config scripts table:");
|
console.log("Getting SQLite config table:");
|
||||||
console.log(response);
|
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_nmp5channels = document.getElementById("check_NPM_5channels");
|
||||||
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"];
|
const checkbox_bme = document.getElementById("check_bme280");
|
||||||
checkbox_bme.checked = response["BME280/get_data_v2.py"];
|
const checkbox_CO2 = document.getElementById("check_CO2");
|
||||||
checkbox_SFA30.checked = response["sensirion/SFA30_read.py"];
|
const checkbox_SFA30 = document.getElementById("check_sensirionSFA30");
|
||||||
checkbox_CO2.checked = response["MH-Z19/write_data.py"];
|
|
||||||
|
|
||||||
//si sonde envea is true
|
checkbox_nmp5channels.checked = response.npm_5channel;
|
||||||
if (response["envea/read_value_v2.py"]) {
|
checkbox_bme.checked = response["BME280"];
|
||||||
add_sondeEnveaContainer();
|
checkbox_SFA30.checked = response["SFA30"];
|
||||||
|
checkbox_CO2.checked = response["MH-Z19"];
|
||||||
|
|
||||||
}
|
//si sonde envea is true
|
||||||
},
|
if (response["envea"]) {
|
||||||
error: function(xhr, status, error) {
|
add_sondeEnveaContainer();
|
||||||
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 the time difference
|
},
|
||||||
const timeDiff = response.time_difference_seconds;
|
error: function(xhr, status, error) {
|
||||||
|
console.error('AJAX request failed:', status, error);
|
||||||
|
}
|
||||||
|
});//end AJAX
|
||||||
|
|
||||||
// Reference to the alert container
|
|
||||||
const alertContainer = document.getElementById("alert_container");
|
|
||||||
|
|
||||||
// Remove any previous alert
|
|
||||||
alertContainer.innerHTML = "";
|
|
||||||
|
|
||||||
// Add an alert based on time difference
|
//get system time and RTC module
|
||||||
if (typeof timeDiff === "number") {
|
$.ajax({
|
||||||
if (timeDiff >= 0 && timeDiff <= 10) {
|
url: 'launcher.php?type=sys_RTC_module_time',
|
||||||
alertContainer.innerHTML = `
|
dataType: 'json', // Specify that you expect a JSON response
|
||||||
<div class="alert alert-success" role="alert">
|
method: 'GET', // Use GET or POST depending on your needs
|
||||||
RTC and system time are in sync (Difference: ${timeDiff} sec).
|
success: function(response) {
|
||||||
</div>`;
|
console.log(response);
|
||||||
} else if (timeDiff > 10) {
|
// Update the input fields with the received JSON data
|
||||||
alertContainer.innerHTML = `
|
document.getElementById("sys_local_time").value = response.system_local_time;
|
||||||
<div class="alert alert-danger" role="alert">
|
document.getElementById("sys_UTC_time").value = response.system_utc_time;
|
||||||
RTC time is out of sync! (Difference: ${timeDiff} sec).
|
document.getElementById("RTC_utc_time").value = response.rtc_module_time;
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});//end ajax
|
|
||||||
|
|
||||||
//get local RTC
|
// Get the time difference
|
||||||
$.ajax({
|
const timeDiff = response.time_difference_seconds;
|
||||||
url: 'launcher.php?type=RTC_time',
|
|
||||||
dataType: 'text', // Specify that you expect a JSON response
|
// Reference to the alert container
|
||||||
method: 'GET', // Use GET or POST depending on your needs
|
const alertContainer = document.getElementById("alert_container");
|
||||||
success: function(response) {
|
|
||||||
console.log("Local RTC: " + response);
|
// Remove any previous alert
|
||||||
const RTC_Element = document.getElementById("RTC_time");
|
alertContainer.innerHTML = "";
|
||||||
RTC_Element.textContent = response;
|
|
||||||
},
|
// Add an alert based on time difference
|
||||||
error: function(xhr, status, error) {
|
if (typeof timeDiff === "number") {
|
||||||
console.error('AJAX request failed:', status, error);
|
if (timeDiff >= 0 && timeDiff <= 10) {
|
||||||
}
|
alertContainer.innerHTML = `
|
||||||
});//end ajax
|
<div class="alert alert-success" role="alert">
|
||||||
|
RTC and system time are in sync (Difference: ${timeDiff} sec).
|
||||||
|
</div>`;
|
||||||
|
} else if (timeDiff > 10) {
|
||||||
|
alertContainer.innerHTML = `
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
RTC time is out of sync! (Difference: ${timeDiff} sec).
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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
|
} //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 = `
|
|
||||||
<strong>Success!</strong><br>
|
|
||||||
Parameter: ${response.script_path || param}<br>
|
|
||||||
Value: ${response.enabled !== undefined ? response.enabled : value}<br>
|
|
||||||
${response.message || ''}
|
|
||||||
`;
|
|
||||||
|
|
||||||
if (response.script_path == "envea/read_value_v2.py") {
|
|
||||||
console.log("envea sondes activated");
|
|
||||||
add_sondeEnveaContainer();
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Error message
|
|
||||||
toastLiveExample.classList.remove('text-bg-success');
|
|
||||||
toastLiveExample.classList.add('text-bg-danger');
|
|
||||||
|
|
||||||
formattedMessage = `
|
|
||||||
<strong>Error!</strong><br>
|
|
||||||
${response.error || 'Unknown error'}<br>
|
|
||||||
Parameter: ${response.script_path || param}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the toast body with formatted content
|
|
||||||
toastBody.innerHTML = formattedMessage;
|
|
||||||
// Show the toast
|
|
||||||
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample)
|
|
||||||
toastBootstrap.show()
|
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('AJAX request failed:', status, error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function update_config(param, value){
|
|
||||||
console.log("Updating ",param," : ", value);
|
|
||||||
$.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(){
|
function updateGitPull(){
|
||||||
console.log("Updating device (git pull)");
|
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 = '<i class="bi bi-arrow-clockwise"></i> Restart';
|
||||||
|
restartBtn.onclick = function() {
|
||||||
|
restartService(service.name);
|
||||||
|
};
|
||||||
|
actionsCell.appendChild(restartBtn);
|
||||||
|
|
||||||
|
// Enable/Disable button
|
||||||
|
const toggleBtn = document.createElement('button');
|
||||||
|
toggleBtn.className = `btn btn-sm ${service.enabled ? 'btn-danger' : 'btn-success'}`;
|
||||||
|
toggleBtn.innerHTML = service.enabled ? '<i class="bi bi-stop"></i> Disable' : '<i class="bi bi-play"></i> Enable';
|
||||||
|
toggleBtn.onclick = function() {
|
||||||
|
toggleService(service.name, !service.enabled);
|
||||||
|
};
|
||||||
|
actionsCell.appendChild(toggleBtn);
|
||||||
|
|
||||||
|
row.appendChild(actionsCell);
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showServiceError(message) {
|
||||||
|
const tbody = document.getElementById('services-tbody');
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" class="text-center text-danger">
|
||||||
|
<i class="bi bi-exclamation-triangle"></i> ${message}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartService(serviceName) {
|
||||||
|
console.log(`Restarting service: ${serviceName}`);
|
||||||
|
|
||||||
|
if (!confirm(`Are you sure you want to restart ${serviceName}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastLiveExample = document.getElementById('liveToast');
|
||||||
|
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'launcher.php?type=restart_systemd_service&service=' + encodeURIComponent(serviceName),
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'GET',
|
||||||
|
cache: false,
|
||||||
|
success: function(response) {
|
||||||
|
console.log('Service restart response:', response);
|
||||||
|
|
||||||
|
let formattedMessage = '';
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// Success message
|
||||||
|
toastLiveExample.classList.remove('text-bg-danger');
|
||||||
|
toastLiveExample.classList.add('text-bg-success');
|
||||||
|
|
||||||
|
formattedMessage = `
|
||||||
|
<strong>Success!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${response.message || 'Service restarted successfully'}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Refresh services after a short delay
|
||||||
|
setTimeout(refreshServices, 2000);
|
||||||
|
} else {
|
||||||
|
// Error message
|
||||||
|
toastLiveExample.classList.remove('text-bg-success');
|
||||||
|
toastLiveExample.classList.add('text-bg-danger');
|
||||||
|
|
||||||
|
formattedMessage = `
|
||||||
|
<strong>Error!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${response.error || 'Unknown error occurred'}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update and show toast
|
||||||
|
toastBody.innerHTML = formattedMessage;
|
||||||
|
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||||
|
toastBootstrap.show();
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Failed to restart service:', error);
|
||||||
|
|
||||||
|
// Show error toast
|
||||||
|
toastLiveExample.classList.remove('text-bg-success');
|
||||||
|
toastLiveExample.classList.add('text-bg-danger');
|
||||||
|
toastBody.innerHTML = `
|
||||||
|
<strong>Request Failed!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
Error: ${error}
|
||||||
|
`;
|
||||||
|
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||||
|
toastBootstrap.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleService(serviceName, enable) {
|
||||||
|
const action = enable ? 'enable' : 'disable';
|
||||||
|
console.log(`${action} service: ${serviceName}`);
|
||||||
|
|
||||||
|
if (!confirm(`Are you sure you want to ${action} ${serviceName}?`)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toastLiveExample = document.getElementById('liveToast');
|
||||||
|
const toastBody = toastLiveExample.querySelector('.toast-body');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'launcher.php?type=toggle_systemd_service&service=' + encodeURIComponent(serviceName) + '&enable=' + enable,
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'GET',
|
||||||
|
cache: false,
|
||||||
|
success: function(response) {
|
||||||
|
console.log('Service toggle response:', response);
|
||||||
|
|
||||||
|
let formattedMessage = '';
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
// Success message
|
||||||
|
toastLiveExample.classList.remove('text-bg-danger');
|
||||||
|
toastLiveExample.classList.add('text-bg-success');
|
||||||
|
|
||||||
|
formattedMessage = `
|
||||||
|
<strong>Success!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${response.message || `Service ${action}d successfully`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Refresh services after a short delay
|
||||||
|
setTimeout(refreshServices, 2000);
|
||||||
|
} else {
|
||||||
|
// Error message
|
||||||
|
toastLiveExample.classList.remove('text-bg-success');
|
||||||
|
toastLiveExample.classList.add('text-bg-danger');
|
||||||
|
|
||||||
|
formattedMessage = `
|
||||||
|
<strong>Error!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
${response.error || 'Unknown error occurred'}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update and show toast
|
||||||
|
toastBody.innerHTML = formattedMessage;
|
||||||
|
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||||
|
toastBootstrap.show();
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('Failed to toggle service:', error);
|
||||||
|
|
||||||
|
// Show error toast
|
||||||
|
toastLiveExample.classList.remove('text-bg-success');
|
||||||
|
toastLiveExample.classList.add('text-bg-danger');
|
||||||
|
toastBody.innerHTML = `
|
||||||
|
<strong>Request Failed!</strong><br>
|
||||||
|
Service: ${serviceName}<br>
|
||||||
|
Error: ${error}
|
||||||
|
`;
|
||||||
|
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastLiveExample);
|
||||||
|
toastBootstrap.show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -664,3 +664,215 @@ if ($type == "wifi_scan_old") {
|
|||||||
echo $json_data;
|
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()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -105,15 +105,6 @@ from datetime import datetime
|
|||||||
# Record the start time of the script
|
# Record the start time of the script
|
||||||
start_time_script = time.time()
|
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 to be sent to data.moduleair.fr
|
||||||
payload_csv = [None] * 25
|
payload_csv = [None] * 25
|
||||||
#Payload JSON to be sent to uSpot
|
#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")
|
conn = sqlite3.connect("/var/www/moduleair_pro_4g/sqlite/sensors.db")
|
||||||
cursor = conn.cursor()
|
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
|
#get config data from SQLite table
|
||||||
def load_config_sqlite():
|
def load_config_sqlite():
|
||||||
@@ -165,32 +200,7 @@ def load_config_sqlite():
|
|||||||
print(f"Error loading config from SQLite: {e}")
|
print(f"Error loading config from SQLite: {e}")
|
||||||
return {}
|
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
|
#Load config
|
||||||
config = load_config_sqlite()
|
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
|
npm_5channel = config.get('npm_5channel', False) #5 canaux du NPM
|
||||||
selected_networkID = int(config.get('SARA_R4_neworkID', 0))
|
selected_networkID = int(config.get('SARA_R4_neworkID', 0))
|
||||||
send_uSpot = config.get('send_uSpot', False) #envoi sur MicroSpot ()
|
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
|
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
|
#update device id in the payload json
|
||||||
payload_json["moduleairid"] = device_id
|
payload_json["moduleairid"] = device_id
|
||||||
|
|
||||||
@@ -461,6 +467,16 @@ try:
|
|||||||
'''
|
'''
|
||||||
print('<h3>START LOOP</h3>')
|
print('<h3>START LOOP</h3>')
|
||||||
|
|
||||||
|
# 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
|
#Local timestamp
|
||||||
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
row = cursor.fetchone() # Get the first (and only) row
|
row = cursor.fetchone() # Get the first (and only) row
|
||||||
@@ -561,33 +577,6 @@ try:
|
|||||||
else:
|
else:
|
||||||
print("No data available in the database.")
|
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")
|
#print("Verify SARA R4 connection")
|
||||||
|
|
||||||
@@ -662,6 +651,9 @@ try:
|
|||||||
# On vérifie si le signal n'est pas à 99 pour déconnexion
|
# On vérifie si le signal n'est pas à 99 pour déconnexion
|
||||||
# si c'est le cas on essaie de se reconnecter
|
# si c'est le cas on essaie de se reconnecter
|
||||||
if signal_quality == 99:
|
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('<span style="color: red;font-weight: bold;">⚠️ATTENTION: Signal Quality indicates no signal (99)⚠️</span>')
|
||||||
print("TRY TO RECONNECT:")
|
print("TRY TO RECONNECT:")
|
||||||
command = f'AT+COPS=1,2,"{selected_networkID}"\r'
|
command = f'AT+COPS=1,2,"{selected_networkID}"\r'
|
||||||
@@ -678,6 +670,8 @@ try:
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
else:
|
else:
|
||||||
print("Signal Quality:", signal_quality)
|
print("Signal Quality:", signal_quality)
|
||||||
|
update_config_sqlite('SARA_signal_quality', signal_quality)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -776,6 +770,7 @@ try:
|
|||||||
print("*****")
|
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("*****")
|
print("*****")
|
||||||
|
update_config_sqlite('SARA_network_status', 'error')
|
||||||
|
|
||||||
|
|
||||||
# Get error code
|
# Get error code
|
||||||
@@ -838,6 +833,8 @@ try:
|
|||||||
# Si la commande HTTP a réussi
|
# 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>')
|
||||||
|
|
||||||
|
#update SARA_network_status
|
||||||
|
update_config_sqlite('SARA_network_status', 'connected')
|
||||||
|
|
||||||
#4. Read reply from server
|
#4. Read reply from server
|
||||||
print("Reply from server:")
|
print("Reply from server:")
|
||||||
|
|||||||
BIN
matrix/screenNetwork/network_status
Normal file
BIN
matrix/screenNetwork/network_status
Normal file
Binary file not shown.
489
matrix/screenNetwork/network_status.cc
Normal file
489
matrix/screenNetwork/network_status.cc
Normal file
@@ -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 <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <atomic>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <sstream>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <chrono>
|
||||||
|
#include <ctime>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
using rgb_matrix::RGBMatrix;
|
||||||
|
using rgb_matrix::Canvas;
|
||||||
|
|
||||||
|
std::atomic<bool> 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;
|
||||||
|
}
|
||||||
15
services/matrix_boot.sh
Normal file
15
services/matrix_boot.sh
Normal file
@@ -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"
|
||||||
@@ -206,52 +206,46 @@ WantedBy=timers.target
|
|||||||
EOL
|
EOL
|
||||||
|
|
||||||
# Create service and timer files for LED Matrix Display
|
# 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]
|
[Unit]
|
||||||
Description=moduleair LED Matrix Display Service
|
Description=ModuleAir Matrix Boot Display
|
||||||
After=network.target
|
After=network.target
|
||||||
|
After=multi-user.target
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=oneshot
|
Type=simple
|
||||||
ExecStart=/var/www/moduleair_pro_4g/matrix/imageScreen/image_split /var/www/moduleair_pro_4g/matrix/imageScreen/ModuleAir128x64.png
|
ExecStart=/var/www/moduleair_pro_4g/services/matrix_boot.sh
|
||||||
User=root
|
User=root
|
||||||
WorkingDirectory=/var/www/moduleair_pro_4g/matrix/imageScreen/
|
WorkingDirectory=/var/www/moduleair_pro_4g/
|
||||||
StandardOutput=append:/var/www/moduleair_pro_4g/logs/matrix_service.log
|
StandardOutput=append:/var/www/moduleair_pro_4g/logs/matrix_boot.log
|
||||||
StandardError=append:/var/www/moduleair_pro_4g/logs/matrix_service_errors.log
|
StandardError=append:/var/www/moduleair_pro_4g/logs/matrix_boot.log
|
||||||
|
TimeoutStartSec=120s
|
||||||
|
RemainAfterExit=yes
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
EOL
|
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
|
# Make sure the matrix executable has proper permissions
|
||||||
echo "Setting permissions for LED matrix executable..."
|
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/imageScreen/image_split
|
||||||
|
chmod +x /var/www/moduleair_pro_4g/matrix/screenSensors/displayAll4_v2
|
||||||
|
|
||||||
# Reload systemd to recognize new services
|
# Reload systemd to recognize new services
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
# Enable and start all timers
|
# Enable and start all timers
|
||||||
echo "Enabling and starting all services..."
|
echo "Enabling and starting all services..."
|
||||||
for service in npm envea sara bme280 co2 db-cleanup matrix-display; do
|
for service in npm envea sara bme280 co2 db-cleanup; do
|
||||||
systemctl enable moduleair-$service-data.timer 2>/dev/null || systemctl enable moduleair-$service.timer
|
systemctl enable moduleair-$service-data.timer
|
||||||
systemctl start moduleair-$service-data.timer 2>/dev/null || systemctl start moduleair-$service.timer
|
systemctl start moduleair-$service-data.timer
|
||||||
echo "Started moduleair-$service timer"
|
echo "Started moduleair-$service-data timer"
|
||||||
done
|
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..."
|
echo "Checking status of all timers..."
|
||||||
systemctl list-timers | grep moduleair
|
systemctl list-timers | grep moduleair
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ config_entries = [
|
|||||||
("modem_config_mode", "0", "bool"),
|
("modem_config_mode", "0", "bool"),
|
||||||
("deviceID", "XXXX", "str"),
|
("deviceID", "XXXX", "str"),
|
||||||
("npm_5channel", "0", "bool"),
|
("npm_5channel", "0", "bool"),
|
||||||
|
("BME280", "0", "bool"),
|
||||||
|
("SFA30", "0", "bool"),
|
||||||
|
("MHZ19", "0", "bool"),
|
||||||
|
("envea", "0", "bool"),
|
||||||
("latitude_raw", "0", "int"),
|
("latitude_raw", "0", "int"),
|
||||||
("longitude_raw", "0", "int"),
|
("longitude_raw", "0", "int"),
|
||||||
("latitude_precision", "0", "int"),
|
("latitude_precision", "0", "int"),
|
||||||
@@ -79,10 +83,11 @@ config_entries = [
|
|||||||
("SaraR4_baudrate", "115200", "int"),
|
("SaraR4_baudrate", "115200", "int"),
|
||||||
("NPM_solo_port", "/dev/ttyAMA5", "str"),
|
("NPM_solo_port", "/dev/ttyAMA5", "str"),
|
||||||
("sshTunnel_port", "59228", "int"),
|
("sshTunnel_port", "59228", "int"),
|
||||||
("SARA_R4_general_status", "connected", "str"),
|
("SARA_general_status", "connected", "str"),
|
||||||
("SARA_R4_SIM_status", "connected", "str"),
|
("SARA_SIM_status", "connected", "str"),
|
||||||
("SARA_R4_network_status", "connected", "str"),
|
("SARA_network_status", "connected", "str"),
|
||||||
("SARA_R4_neworkID", "20810", "int"),
|
("SARA_signal_quality", "22", "int"),
|
||||||
|
("SARA_neworkID", "20810", "int"),
|
||||||
("WIFI_status", "connected", "str"),
|
("WIFI_status", "connected", "str"),
|
||||||
("send_uSpot", "0", "bool"),
|
("send_uSpot", "0", "bool"),
|
||||||
("windMeter", "0", "bool"),
|
("windMeter", "0", "bool"),
|
||||||
|
|||||||
Reference in New Issue
Block a user