Files
nebuleair_pro_4g/html/sensors.html
PaulVua 906eaa851d Fix BME280 and sensor cards not displaying
- Refactor sensor card creation to use config_table instead of non-existent config_scripts_table
- Fix BME280 card check: use config.BME280 instead of response["BME280/get_data_v2.py"]
- Fix NOISE card check: use config.NOISE instead of response.i2C_sound
- Fix Envea card check: use config.envea instead of response["envea/read_value_v2.py"]
- Create dedicated createSensorCards() function for cleaner code
- Remove obsolete get_config_scripts_sqlite AJAX call

Now sensor cards properly display based on config_table settings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-05 17:49:02 +01:00

458 lines
17 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NebuleAir</title>
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<style>
body {
overflow-x: hidden;
}
#sidebar a.nav-link {
position: relative;
display: flex;
align-items: center;
}
#sidebar a.nav-link:hover {
background-color: rgba(0, 0, 0, 0.5);
}
#sidebar a.nav-link svg {
margin-right: 8px; /* Add spacing between icons and text */
}
#sidebar {
transition: transform 0.3s ease-in-out;
}
.offcanvas-backdrop {
z-index: 1040;
}
</style>
</head>
<body>
<!-- Topbar -->
<span id="topbar"></span>
<!-- Sidebar Offcanvas for Mobile -->
<div class="offcanvas offcanvas-start text-white bg-dark" tabindex="-1" id="sidebarOffcanvas" aria-labelledby="sidebarOffcanvasLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="sidebarOffcanvasLabel">NebuleAir</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body" id="sidebar_mobile">
</div>
</div>
<div class="container-fluid mt-5">
<div class="row">
<aside class="col-md-2 col-lg-1 d-none d-md-block vh-100 position-fixed bg-dark text-white" id="sidebar">
</aside>
<!-- Main content -->
<main class="col-md-10 ms-sm-auto col-lg-11 offset-md-2 offset-lg-1 px-md-4">
<h1 class="mt-4">Les sondes de mesure</h1>
<p>Votre capteur NebuleAir est équipé de une ou plusieurs sondes qui permettent de mesurer certaines variables environnementales. La mesure
est automatique mais vous pouvez ici vous assurer de leur bon fonctionnement.
</p>
<div class="row mb-3" id="card-container"></div>
</main>
</div>
</div>
<!-- JAVASCRIPT -->
<!-- Link Ajax locally -->
<script src="assets/jquery/jquery-3.7.1.min.js"></script>
<!-- Link Bootstrap JS and Popper.js locally -->
<script src="assets/js/bootstrap.bundle.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const elementsToLoad = [
{ id: 'topbar', file: 'topbar.html' },
{ id: 'sidebar', file: 'sidebar.html' },
{ id: 'sidebar_mobile', file: 'sidebar.html' }
];
elementsToLoad.forEach(({ id, file }) => {
fetch(file)
.then(response => response.text())
.then(data => {
const element = document.getElementById(id);
if (element) {
element.innerHTML = data;
}
})
.catch(error => console.error(`Error loading ${file}:`, error));
});
});
function getNPM_values(port){
console.log("Data from NPM (port "+port+"):");
$("#loading_"+port).show();
$.ajax({
url: 'launcher.php?type=npm&port='+port,
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);
const tableBody = document.getElementById("data-table-body_"+port);
tableBody.innerHTML = "";
$("#loading_"+port).hide();
// Create an array of the desired keys
const keysToShow = ["PM1", "PM25", "PM10","message"];
// Error messages mapping
const errorMessages = {
"notReady": "Sensor is not ready",
"fanError": "Fan malfunction detected",
"laserError": "Laser malfunction detected",
"heatError": "Heating system error",
"t_rhError": "Temperature/Humidity sensor error",
"memoryError": "Memory failure detected",
"degradedState": "Sensor in degraded state"
};
// Add only the specified elements to the table
keysToShow.forEach(key => {
if (response[key] !== undefined) { // Check if the key exists in the response
const value = response[key];
$("#data-table-body_"+port).append(`
<tr>
<td>${key}</td>
<td>${value} µg/m³</td>
</tr>
`);
}
});
// Check for errors and add them to the table
Object.keys(errorMessages).forEach(errorKey => {
if (response[errorKey] === 1) {
$("#data-table-body_" + port).append(`
<tr class="error-row">
<td><b>${errorKey}</b></td>
<td style="color: red;">⚠ ${errorMessages[errorKey]}</td>
</tr>
`);
}
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function getENVEA_values(port, name){
console.log("Data from Envea " + name + " (port " + port + "):");
$("#loading_envea" + name).show();
$.ajax({
url: 'launcher.php?type=envea&port=' + port + '&name=' + name,
dataType: 'json',
method: 'GET',
success: function(response) {
console.log(response);
const tableBody = document.getElementById("data-table-body_envea" + name);
tableBody.innerHTML = "";
$("#loading_envea" + name).hide();
const keysToShow = [name];
keysToShow.forEach(key => {
if (response !== undefined) {
const value = response;
$("#data-table-body_envea" + name).append(`
<tr>
<td>${key}</td>
<td>${value} ppb</td>
</tr>
`);
}
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
const tableBody = document.getElementById("data-table-body_envea" + name);
$("#loading_envea" + name).hide();
tableBody.innerHTML = `
<tr>
<td colspan="2" class="text-danger">
❌ Error: unable to get data from sensor.<br>
<small>${status}: ${error}</small>
</td>
</tr>
`;
}
});
}
function getNoise_values(){
console.log("Data from I2C Noise Sensor:");
$("#loading_noise").show();
$.ajax({
url: 'launcher.php?type=noise',
dataType: 'text',
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
const tableBody = document.getElementById("data-table-body_noise");
tableBody.innerHTML = "";
$("#loading_noise").hide();
// Create an array of the desired keys
const keysToShow = ["Noise"];
// Add only the specified elements to the table
keysToShow.forEach(key => {
if (response !== undefined) { // Check if the key exists in the response
const value = response;
$("#data-table-body_noise").append(`
<tr>
<td>${key}</td>
<td>${value} DB</td>
</tr>
`);
}
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
function getBME280_values(){
console.log("Data from I2C BME280:");
$("#loading_BME280").show();
$.ajax({
url: 'launcher.php?type=BME280',
dataType: 'text',
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log(response);
const tableBody = document.getElementById("data-table-body_BME280");
tableBody.innerHTML = "";
$("#loading_BME280").hide();
// Parse the JSON response
const data = JSON.parse(response);
const keysToShow = ["temp", "hum", "press"];
// Add only the specified elements to the table
keysToShow.forEach(key => {
if (response !== undefined) { // Check if the key exists in the response
const value = data[key];
const unit = key === "temp" ? "°C"
: key === "hum" ? "%"
: key === "press" ? "hPa"
: ""; // Add appropriate units
$("#data-table-body_BME280").append(`
<tr>
<td>${key.charAt(0).toUpperCase() + key.slice(1)}</td>
<td>${value} ${unit}</td>
</tr>
`);
}
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
}
window.onload = function() {
//NEW way to get config (SQLite)
let mainConfig = {}; // Store main config for use in sensor card creation
$.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);
mainConfig = response; // Store for later use
//device name_side bar
const elements = document.querySelectorAll('.sideBar_sensorName');
elements.forEach((element) => {
element.innerText = response.deviceName;
});
// After getting main config, create sensor cards
createSensorCards(mainConfig);
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX
//Function to create sensor cards based on config
function createSensorCards(config) {
console.log("Creating sensor cards with config:");
console.log(config);
const container = document.getElementById('card-container'); // Conteneur des cartes
//creates NPM card (by default)
const cardHTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
Port UART
</div>
<div class="card-body">
<h5 class="card-title">NextPM</h5>
<p class="card-text">Capteur particules fines.</p>
<button class="btn btn-primary" onclick="getNPM_values('ttyAMA5')">Get Data</button>
<br>
<div id="loading_ttyAMA5" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_ttyAMA5"></tbody>
</table>
</div>
</div>
</div>`;
container.innerHTML += cardHTML; // Add the I2C card if condition is met
//creates i2c BME280 card
if (config.BME280) {
const i2C_BME_HTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
Port I2C
</div>
<div class="card-body">
<h5 class="card-title">BME280 Temp/Hum sensor</h5>
<p class="card-text">Capteur température et humidité sur le port I2C.</p>
<button class="btn btn-primary mb-1" onclick="getBME280_values()">Get Data</button>
<br>
<div id="loading_BME280" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_BME280"></tbody>
</table>
</div>
</div>
</div>`;
container.innerHTML += i2C_BME_HTML; // Add the I2C card if condition is met
}
//creates i2c sound card
if (config.NOISE) {
const i2C_HTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
Port I2C
</div>
<div class="card-body">
<h5 class="card-title">Decibel Meter</h5>
<p class="card-text">Capteur bruit sur le port I2C.</p>
<button class="btn btn-primary mb-1" onclick="getNoise_values()">Get Data</button>
<br>
<button class="btn btn-success" onclick="startNoise()">Start recording</button>
<button class="btn btn-danger" onclick="stopNoise()">Stop recording</button>
<div id="loading_noise" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_noise"></tbody>
</table>
</div>
</div>
</div>`;
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
}
//Si on a des SONDES ENVEA connectée il faut faire un deuxième call dans la table envea_sondes_table
//creates ENVEA cards
if (config.envea) {
console.log("Need to display ENVEA sondes");
//getting config_scripts table
$.ajax({
url: 'launcher.php?type=get_envea_sondes_table_sqlite',
dataType:'json',
//dataType: 'json', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(sondes) {
console.log("Getting SQLite envea sondes table:");
console.log(sondes);
const ENVEA_sensors = sondes.filter(sonde => sonde.connected); // Filter only connected sondes
ENVEA_sensors.forEach((sensor, index) => {
const port = sensor.port; // Port from the sensor object
const name = sensor.name; // Port from the sensor object
const coefficient = sensor.coefficient;
const cardHTML = `
<div class="col-sm-3">
<div class="card">
<div class="card-header">
Port UART ${port.replace('ttyAMA', '')}
</div>
<div class="card-body">
<h5 class="card-title">Sonde Envea ${name}</h5>
<p class="card-text">Capteur gas.</p>
<button class="btn btn-primary" onclick="getENVEA_values('${port}','${name}')">Get Data</button>
<div id="loading_envea${name}" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
<table class="table table-striped-columns">
<tbody id="data-table-body_envea${name}"></tbody>
</table>
</div>
</div>
</div>`;
container.innerHTML += cardHTML; // Ajouter la carte au conteneur
});
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});//end AJAX envea Sondes
}//end if envea
} // end createSensorCards function
//get local RTC
$.ajax({
url: 'launcher.php?type=RTC_time',
dataType: 'text', // Specify that you expect a JSON response
method: 'GET', // Use GET or POST depending on your needs
success: function(response) {
console.log("Local RTC: " + response);
const RTC_Element = document.getElementById("RTC_time");
RTC_Element.textContent = response;
},
error: function(xhr, status, error) {
console.error('AJAX request failed:', status, error);
}
});
} //end windows onload
</script>
</body>
</html>