v1.10.0: intégration capteur CCS811 (TVOC/eCO2, I2C)
Nouveau capteur de qualité d'air CCS811 sur le bus I2C, calqué sur le pattern S88 (local-only, pas encore dans le payload de transmission). - CCS811/get_data.py (lecture live) + write_data.py (timer 10s, self-heal table) - table data_CCS811 (timestamp, eCO2, TVOC) dans create_db.py - config CCS811 (bool) + CCS811_address (0x5A/0x5B, défaut 0x5A) dans set_config.py - service+timer systemd nebuleair-ccs811-data (10s) + ajout boucle d'activation - admin.html: case d'activation + dropdown adresse I2C - sensors.html: carte Get Data (TVOC + eCO2) - database.html + launcher.php: consultation/export/stats data_CCS811 - lib adafruit-circuitpython-ccs811 dans installation_part1.sh - CCS811/README.md: câblage, adresses, warning clock-stretching I2C sur Pi - CLAUDE.md + changelog mis à jour Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -154,6 +154,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_ccs811" onchange="update_config_sqlite('CCS811', this.checked)">
|
||||
<label class="form-check-label" for="check_ccs811">
|
||||
Send VOC sensor data (CCS811)
|
||||
</label>
|
||||
<div class="mt-2 ms-4" style="max-width: 250px;">
|
||||
<label for="ccs811_address" class="form-label small mb-1">Adresse I2C du capteur CCS811</label>
|
||||
<select class="form-select form-select-sm" id="ccs811_address" onchange="update_config_sqlite('CCS811_address', this.value)">
|
||||
<option value="0x5A">0x5A (Adafruit, ADDR=GND)</option>
|
||||
<option value="0x5B">0x5B (SparkFun, ADDR=VDD)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-check mb-3">
|
||||
<input class="form-check-input" type="checkbox" value="" id="check_wifi_power_saving" onchange="update_config_sqlite('wifi_power_saving', this.checked)">
|
||||
<label class="form-check-label" for="check_wifi_power_saving">
|
||||
@@ -596,6 +610,7 @@ window.onload = function() {
|
||||
const checkbox_noise = document.getElementById("check_NOISE");
|
||||
const checkbox_mhz19 = document.getElementById("check_mhz19");
|
||||
const checkbox_s88 = document.getElementById("check_s88");
|
||||
const checkbox_ccs811 = document.getElementById("check_ccs811");
|
||||
const checkbox_wifi_power_saving = document.getElementById("check_wifi_power_saving");
|
||||
|
||||
checkbox_bme.checked = response["BME280"];
|
||||
@@ -609,6 +624,10 @@ window.onload = function() {
|
||||
if (response["S88_port"]) {
|
||||
document.getElementById("s88_port").value = response["S88_port"];
|
||||
}
|
||||
checkbox_ccs811.checked = response["CCS811"];
|
||||
if (response["CCS811_address"]) {
|
||||
document.getElementById("ccs811_address").value = response["CCS811_address"];
|
||||
}
|
||||
checkbox_wifi_power_saving.checked = response["wifi_power_saving"];
|
||||
|
||||
checkbox_uSpot.checked = response["send_uSpot"];
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_MPPT','Batterie')" data-i18n="database.battery">Batterie</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_MHZ19','Mesures CO2 (MH-Z19)')">Mesures CO2 (MH-Z19)</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_S88','Mesures CO2 (Senseair S88)')">Mesures CO2 (Senseair S88)</button>
|
||||
<button class="btn btn-primary mb-2" onclick="openTableModal('data_CCS811','Mesures TVOC/eCO2 (CCS811)')">Mesures TVOC/eCO2 (CCS811)</button>
|
||||
<button class="btn btn-warning mb-2" onclick="openTableModal('timestamp_table','Timestamp Table')" data-i18n="database.timestampTable">Timestamp Table</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,6 +101,7 @@
|
||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_mppt')" data-i18n="database.battery">Batterie</button>
|
||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_MHZ19')">Mesures CO2 (MH-Z19)</button>
|
||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_S88')">Mesures CO2 (Senseair S88)</button>
|
||||
<button class="btn btn-primary mb-2" onclick="downloadByDate('data_CCS811')">Mesures TVOC/eCO2 (CCS811)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,6 +119,7 @@
|
||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_MPPT')" data-i18n="database.battery">Batterie</button>
|
||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_MHZ19')">Mesures CO2 (MH-Z19)</button>
|
||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_S88')">Mesures CO2 (Senseair S88)</button>
|
||||
<button class="btn btn-success mb-2" onclick="downloadFullTable('data_CCS811')">Mesures TVOC/eCO2 (CCS811)</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -320,7 +323,8 @@ function buildTableHeader(table) {
|
||||
data_MPPT: ['Timestamp','Battery Voltage','Battery Current','solar_voltage','solar_power','charger_status'],
|
||||
data_NOISE: ['Timestamp','Curent LEQ','DB_A_value','Status'],
|
||||
data_MHZ19: ['Timestamp','CO2 (ppm)'],
|
||||
data_S88: ['Timestamp','CO2 (ppm)']
|
||||
data_S88: ['Timestamp','CO2 (ppm)'],
|
||||
data_CCS811: ['Timestamp','eCO2 (ppm)','TVOC (ppb)']
|
||||
};
|
||||
return (headers[table] || ['Data']).map(h => `<th>${h}</th>`).join('');
|
||||
}
|
||||
@@ -343,7 +347,7 @@ function buildTableRow(table, columns) {
|
||||
return `<td>${columns[1]}</td>`;
|
||||
}
|
||||
// Default: render all available columns
|
||||
const colCount = { data_BME280: 4, data_NPM_5channels: 6, data_envea: 6, data_WIND: 3, data_MPPT: 6, data_MHZ19: 2, data_S88: 2 };
|
||||
const colCount = { data_BME280: 4, data_NPM_5channels: 6, data_envea: 6, data_WIND: 3, data_MPPT: 6, data_MHZ19: 2, data_S88: 2, data_CCS811: 3 };
|
||||
const n = colCount[table] || columns.length;
|
||||
return columns.slice(0, n).map(c => `<td>${c}</td>`).join('');
|
||||
}
|
||||
@@ -485,6 +489,9 @@ function downloadCSV(response, table) {
|
||||
else if (table === "data_S88") {
|
||||
csvContent += "TimestampUTC,CO2_ppm\n";
|
||||
}
|
||||
else if (table === "data_CCS811") {
|
||||
csvContent += "TimestampUTC,eCO2_ppm,TVOC_ppb\n";
|
||||
}
|
||||
|
||||
// Format rows as CSV
|
||||
rows.forEach(row => {
|
||||
@@ -513,7 +520,8 @@ const tableDisplayNames = {
|
||||
'data_MPPT': 'Batterie (MPPT)',
|
||||
'data_NOISE': 'Bruit',
|
||||
'data_MHZ19': 'CO2 (MH-Z19)',
|
||||
'data_S88': 'CO2 (Senseair S88)'
|
||||
'data_S88': 'CO2 (Senseair S88)',
|
||||
'data_CCS811': 'TVOC/eCO2 (CCS811)'
|
||||
};
|
||||
|
||||
function loadDbStats() {
|
||||
|
||||
@@ -794,7 +794,7 @@ if ($type == "db_table_stats") {
|
||||
$fileSizeMB = round($fileSizeBytes / (1024 * 1024), 2);
|
||||
|
||||
// Sensor data tables to inspect
|
||||
$tables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE', 'data_MHZ19', 'data_S88'];
|
||||
$tables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE', 'data_MHZ19', 'data_S88', 'data_CCS811'];
|
||||
|
||||
$tableStats = [];
|
||||
foreach ($tables as $tableName) {
|
||||
@@ -844,7 +844,7 @@ if ($type == "download_full_table") {
|
||||
$table = $_GET['table'] ?? '';
|
||||
|
||||
// Whitelist of allowed tables
|
||||
$allowedTables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE', 'data_MHZ19', 'data_S88'];
|
||||
$allowedTables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE', 'data_MHZ19', 'data_S88', 'data_CCS811'];
|
||||
|
||||
if (!in_array($table, $allowedTables)) {
|
||||
header('Content-Type: application/json');
|
||||
@@ -862,7 +862,8 @@ if ($type == "download_full_table") {
|
||||
'data_MPPT' => 'TimestampUTC,Battery_voltage,Battery_current,Solar_voltage,Solar_power,Charger_status',
|
||||
'data_NOISE' => 'TimestampUTC,Current_LEQ,DB_A_value',
|
||||
'data_MHZ19' => 'TimestampUTC,CO2_ppm',
|
||||
'data_S88' => 'TimestampUTC,CO2_ppm'
|
||||
'data_S88' => 'TimestampUTC,CO2_ppm',
|
||||
'data_CCS811' => 'TimestampUTC,eCO2_ppm,TVOC_ppb'
|
||||
];
|
||||
|
||||
try {
|
||||
@@ -1016,6 +1017,12 @@ if ($type == "s88") {
|
||||
echo $output;
|
||||
}
|
||||
|
||||
if ($type == "ccs811") {
|
||||
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/CCS811/get_data.py';
|
||||
$output = shell_exec($command);
|
||||
echo $output;
|
||||
}
|
||||
|
||||
|
||||
if ($type == "table_mesure") {
|
||||
$table=$_GET['table'];
|
||||
@@ -1674,6 +1681,10 @@ if ($type == "get_systemd_services") {
|
||||
'description' => 'Reads CO2 concentration from MH-Z19 sensor',
|
||||
'frequency' => 'Every 2 minutes'
|
||||
],
|
||||
'nebuleair-ccs811-data.timer' => [
|
||||
'description' => 'Reads eCO2/TVOC from CCS811 air-quality sensor',
|
||||
'frequency' => 'Every 10 seconds'
|
||||
],
|
||||
'nebuleair-s88-data.timer' => [
|
||||
'description' => 'Reads CO2 concentration from Senseair S88 sensor',
|
||||
'frequency' => 'Every 10 seconds'
|
||||
@@ -1765,6 +1776,7 @@ if ($type == "restart_systemd_service") {
|
||||
'nebuleair-noise-data.timer',
|
||||
'nebuleair-mhz19-data.timer',
|
||||
'nebuleair-s88-data.timer',
|
||||
'nebuleair-ccs811-data.timer',
|
||||
'nebuleair-db-cleanup-data.timer',
|
||||
'nebuleair-wifi-powersave.timer',
|
||||
'nebuleair-cpu-power.service',
|
||||
@@ -1831,6 +1843,7 @@ if ($type == "toggle_systemd_service") {
|
||||
'nebuleair-noise-data.timer',
|
||||
'nebuleair-mhz19-data.timer',
|
||||
'nebuleair-s88-data.timer',
|
||||
'nebuleair-ccs811-data.timer',
|
||||
'nebuleair-db-cleanup-data.timer',
|
||||
'nebuleair-wifi-powersave.timer',
|
||||
'nebuleair-cpu-power.service',
|
||||
|
||||
@@ -409,6 +409,62 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getCCS811_values() {
|
||||
console.log("Data from CCS811 air-quality sensor:");
|
||||
$("#loading_ccs811").show();
|
||||
|
||||
$.ajax({
|
||||
url: 'launcher.php?type=ccs811',
|
||||
dataType: 'json',
|
||||
method: 'GET',
|
||||
success: function (response) {
|
||||
console.log(response);
|
||||
const tableBody = document.getElementById("data-table-body_ccs811");
|
||||
tableBody.innerHTML = "";
|
||||
$("#loading_ccs811").hide();
|
||||
|
||||
if (response.error) {
|
||||
$("#data-table-body_ccs811").append(`
|
||||
<tr>
|
||||
<td colspan="2" class="text-danger">
|
||||
⚠ ${response.error}
|
||||
</td>
|
||||
</tr>
|
||||
`);
|
||||
} else {
|
||||
if (response.TVOC !== undefined) {
|
||||
$("#data-table-body_ccs811").append(`
|
||||
<tr>
|
||||
<td>TVOC</td>
|
||||
<td>${response.TVOC} ppb</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
if (response.eCO2 !== undefined) {
|
||||
$("#data-table-body_ccs811").append(`
|
||||
<tr>
|
||||
<td>eCO2</td>
|
||||
<td>${response.eCO2} ppm</td>
|
||||
</tr>
|
||||
`);
|
||||
}
|
||||
}
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
console.error('AJAX request failed:', status, error);
|
||||
$("#loading_ccs811").hide();
|
||||
const tableBody = document.getElementById("data-table-body_ccs811");
|
||||
tableBody.innerHTML = `
|
||||
<tr>
|
||||
<td colspan="2" class="text-danger">
|
||||
⚠ Erreur de communication avec le capteur
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getMHZ19_values() {
|
||||
console.log("Data from MH-Z19 CO2 sensor:");
|
||||
$("#loading_mhz19").show();
|
||||
@@ -675,6 +731,29 @@
|
||||
container.innerHTML += S88_HTML;
|
||||
}
|
||||
|
||||
//creates CCS811 air-quality (eCO2/TVOC) card
|
||||
if (config.CCS811) {
|
||||
const CCS811_HTML = `
|
||||
<div class="col-sm-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
I2C
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">CCS811 (TVOC / eCO2)</h5>
|
||||
<p class="card-text">Capteur de composés organiques volatils.</p>
|
||||
<button class="btn btn-primary mb-1" onclick="getCCS811_values()" data-i18n="common.getData">Get Data</button>
|
||||
<div id="loading_ccs811" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||
<table class="table table-striped-columns">
|
||||
<tbody id="data-table-body_ccs811"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
container.innerHTML += CCS811_HTML;
|
||||
}
|
||||
|
||||
//Si on a des SONDES ENVEA connectée il faut faire un deuxième call dans la table envea_sondes_table
|
||||
//creates ENVEA debug card
|
||||
if (config.envea) {
|
||||
|
||||
Reference in New Issue
Block a user