feat: intégration capteur CO2 MH-Z19
- Scripts MH-Z19/get_data.py (lecture standalone) et write_data.py (écriture SQLite) - Table data_MHZ19, config MHZ19, cleanup et service systemd (120s) - Web UI : carte test sensors, checkbox admin, boutons database + CSV download - SARA_send_data_v2.py non modifié (sera fait dans un second temps) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
58
MH-Z19/get_data.py
Normal file
58
MH-Z19/get_data.py
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
'''
|
||||||
|
Script to get CO2 values from MH-Z19 sensor
|
||||||
|
need parameter: CO2_port
|
||||||
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/MH-Z19/get_data.py ttyAMA4
|
||||||
|
'''
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
parameter = sys.argv[1:]
|
||||||
|
port = '/dev/' + parameter[0]
|
||||||
|
|
||||||
|
ser = serial.Serial(
|
||||||
|
port=port,
|
||||||
|
baudrate=9600,
|
||||||
|
parity=serial.PARITY_NONE,
|
||||||
|
stopbits=serial.STOPBITS_ONE,
|
||||||
|
bytesize=serial.EIGHTBITS,
|
||||||
|
timeout=1
|
||||||
|
)
|
||||||
|
|
||||||
|
READ_CO2_COMMAND = b'\xFF\x01\x86\x00\x00\x00\x00\x00\x79'
|
||||||
|
|
||||||
|
|
||||||
|
def read_co2():
|
||||||
|
ser.write(READ_CO2_COMMAND)
|
||||||
|
time.sleep(2)
|
||||||
|
response = ser.read(9)
|
||||||
|
if len(response) < 9:
|
||||||
|
print("Error: No data or incomplete data received.")
|
||||||
|
return None
|
||||||
|
if response[0] == 0xFF:
|
||||||
|
co2_concentration = response[2] * 256 + response[3]
|
||||||
|
return co2_concentration
|
||||||
|
else:
|
||||||
|
print("Error reading data from sensor.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
co2 = read_co2()
|
||||||
|
if co2 is not None:
|
||||||
|
data = {"CO2": co2}
|
||||||
|
json_data = json.dumps(data)
|
||||||
|
print(json_data)
|
||||||
|
else:
|
||||||
|
print("Failed to get CO2 data.")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Program terminated.")
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
66
MH-Z19/write_data.py
Normal file
66
MH-Z19/write_data.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
'''
|
||||||
|
Script to get CO2 values from MH-Z19 sensor and write to database
|
||||||
|
/usr/bin/python3 /var/www/nebuleair_pro_4g/MH-Z19/write_data.py
|
||||||
|
'''
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
conn = sqlite3.connect("/var/www/nebuleair_pro_4g/sqlite/sensors.db")
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
mh_z19_port = "/dev/ttyAMA4"
|
||||||
|
|
||||||
|
ser = serial.Serial(
|
||||||
|
port=mh_z19_port,
|
||||||
|
baudrate=9600,
|
||||||
|
parity=serial.PARITY_NONE,
|
||||||
|
stopbits=serial.STOPBITS_ONE,
|
||||||
|
bytesize=serial.EIGHTBITS,
|
||||||
|
timeout=1
|
||||||
|
)
|
||||||
|
|
||||||
|
READ_CO2_COMMAND = b'\xFF\x01\x86\x00\x00\x00\x00\x00\x79'
|
||||||
|
|
||||||
|
|
||||||
|
def read_co2():
|
||||||
|
ser.write(READ_CO2_COMMAND)
|
||||||
|
time.sleep(2)
|
||||||
|
response = ser.read(9)
|
||||||
|
if len(response) < 9:
|
||||||
|
print("Error: No data or incomplete data received from CO2 sensor.")
|
||||||
|
return None
|
||||||
|
if response[0] == 0xFF:
|
||||||
|
co2_concentration = response[2] * 256 + response[3]
|
||||||
|
return co2_concentration
|
||||||
|
else:
|
||||||
|
print("Error reading data from CO2 sensor.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
co2 = read_co2()
|
||||||
|
if co2 is not None:
|
||||||
|
# Get RTC time from SQLite
|
||||||
|
cursor.execute("SELECT * FROM timestamp_table LIMIT 1")
|
||||||
|
row = cursor.fetchone()
|
||||||
|
rtc_time_str = row[1]
|
||||||
|
# Save to SQLite
|
||||||
|
cursor.execute('INSERT INTO data_MHZ19 (timestamp, CO2) VALUES (?, ?)', (rtc_time_str, co2))
|
||||||
|
conn.commit()
|
||||||
|
print(f"CO2: {co2} ppm (saved at {rtc_time_str})")
|
||||||
|
else:
|
||||||
|
print("Failed to get CO2 data.")
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Program terminated.")
|
||||||
|
finally:
|
||||||
|
ser.close()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -118,6 +118,13 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" value="" id="check_mhz19" onchange="update_config_sqlite('MHZ19', this.checked)">
|
||||||
|
<label class="form-check-label" for="check_mhz19">
|
||||||
|
Send CO2 data (MH-Z19)
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-check mb-3">
|
<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)">
|
<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">
|
<label class="form-check-label" for="check_wifi_power_saving">
|
||||||
@@ -457,6 +464,7 @@ window.onload = function() {
|
|||||||
const checkbox_envea = document.getElementById("check_envea");
|
const checkbox_envea = document.getElementById("check_envea");
|
||||||
const checkbox_solar = document.getElementById("check_solarBattery");
|
const checkbox_solar = document.getElementById("check_solarBattery");
|
||||||
const checkbox_noise = document.getElementById("check_NOISE");
|
const checkbox_noise = document.getElementById("check_NOISE");
|
||||||
|
const checkbox_mhz19 = document.getElementById("check_mhz19");
|
||||||
const checkbox_wifi_power_saving = document.getElementById("check_wifi_power_saving");
|
const checkbox_wifi_power_saving = document.getElementById("check_wifi_power_saving");
|
||||||
|
|
||||||
checkbox_bme.checked = response["BME280"];
|
checkbox_bme.checked = response["BME280"];
|
||||||
@@ -465,6 +473,7 @@ window.onload = function() {
|
|||||||
checkbox_nmp5channels.checked = response.npm_5channel;
|
checkbox_nmp5channels.checked = response.npm_5channel;
|
||||||
checkbox_wind.checked = response["windMeter"];
|
checkbox_wind.checked = response["windMeter"];
|
||||||
checkbox_noise.checked = response["NOISE"];
|
checkbox_noise.checked = response["NOISE"];
|
||||||
|
checkbox_mhz19.checked = response["MHZ19"];
|
||||||
checkbox_wifi_power_saving.checked = response["wifi_power_saving"];
|
checkbox_wifi_power_saving.checked = response["wifi_power_saving"];
|
||||||
|
|
||||||
checkbox_uSpot.checked = response["send_uSpot"];
|
checkbox_uSpot.checked = response["send_uSpot"];
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',getSelectedLimit(),false)" data-i18n="database.noiseProbe">Sonde bruit</button>
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',getSelectedLimit(),false)" data-i18n="database.noiseProbe">Sonde bruit</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)" data-i18n="database.windProbe">Sonde Vent</button>
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_WIND',getSelectedLimit(),false)" data-i18n="database.windProbe">Sonde Vent</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MPPT',getSelectedLimit(),false)" data-i18n="database.battery">Batterie</button>
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MPPT',getSelectedLimit(),false)" data-i18n="database.battery">Batterie</button>
|
||||||
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MHZ19',getSelectedLimit(),false)">Mesures CO2</button>
|
||||||
<button class="btn btn-warning mb-2" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)" data-i18n="database.timestampTable">Timestamp Table</button>
|
<button class="btn btn-warning mb-2" onclick="get_data_sqlite('timestamp_table',getSelectedLimit(),false)" data-i18n="database.timestampTable">Timestamp Table</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,6 +105,7 @@
|
|||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_envea',10,true, getStartDate(), getEndDate())" data-i18n="database.cairsensProbe">Sonde Cairsens</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',10,true, getStartDate(), getEndDate())" data-i18n="database.noiseProbe">Sonde Bruit</button>
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_NOISE',10,true, getStartDate(), getEndDate())" data-i18n="database.noiseProbe">Sonde Bruit</button>
|
||||||
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_mppt',10,true, getStartDate(), getEndDate())" data-i18n="database.battery">Batterie</button>
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_mppt',10,true, getStartDate(), getEndDate())" data-i18n="database.battery">Batterie</button>
|
||||||
|
<button class="btn btn-primary mb-2" onclick="get_data_sqlite('data_MHZ19',10,true, getStartDate(), getEndDate())">Mesures CO2</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -358,7 +360,12 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
<th>Timestamp</th>
|
<th>Timestamp</th>
|
||||||
<th>Curent LEQ</th>
|
<th>Curent LEQ</th>
|
||||||
<th>DB_A_value</th>
|
<th>DB_A_value</th>
|
||||||
|
|
||||||
|
`;
|
||||||
|
}else if (table === "data_MHZ19") {
|
||||||
|
tableHTML += `
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>CO2 (ppm)</th>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +440,12 @@ function get_data_sqlite(table, limit, download , startDate = "", endDate = "")
|
|||||||
<td>${columns[0]}</td>
|
<td>${columns[0]}</td>
|
||||||
<td>${columns[1]}</td>
|
<td>${columns[1]}</td>
|
||||||
<td>${columns[2]}</td>
|
<td>${columns[2]}</td>
|
||||||
|
|
||||||
|
`;
|
||||||
|
}else if (table === "data_MHZ19") {
|
||||||
|
tableHTML += `
|
||||||
|
<td>${columns[0]}</td>
|
||||||
|
<td>${columns[1]}</td>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,6 +492,9 @@ function downloadCSV(response, table) {
|
|||||||
else if (table === "data_NPM_5channels") {
|
else if (table === "data_NPM_5channels") {
|
||||||
csvContent += "TimestampUTC,PM_ch1,PM_ch2,PM_ch3,PM_ch4,PM_ch5\n";
|
csvContent += "TimestampUTC,PM_ch1,PM_ch2,PM_ch3,PM_ch4,PM_ch5\n";
|
||||||
}
|
}
|
||||||
|
else if (table === "data_MHZ19") {
|
||||||
|
csvContent += "TimestampUTC,CO2_ppm\n";
|
||||||
|
}
|
||||||
|
|
||||||
// Format rows as CSV
|
// Format rows as CSV
|
||||||
rows.forEach(row => {
|
rows.forEach(row => {
|
||||||
|
|||||||
@@ -542,7 +542,7 @@ if ($type == "db_table_stats") {
|
|||||||
$fileSizeMB = round($fileSizeBytes / (1024 * 1024), 2);
|
$fileSizeMB = round($fileSizeBytes / (1024 * 1024), 2);
|
||||||
|
|
||||||
// Sensor data tables to inspect
|
// Sensor data tables to inspect
|
||||||
$tables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE'];
|
$tables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE', 'data_MHZ19'];
|
||||||
|
|
||||||
$tableStats = [];
|
$tableStats = [];
|
||||||
foreach ($tables as $tableName) {
|
foreach ($tables as $tableName) {
|
||||||
@@ -589,7 +589,7 @@ if ($type == "download_full_table") {
|
|||||||
$table = $_GET['table'] ?? '';
|
$table = $_GET['table'] ?? '';
|
||||||
|
|
||||||
// Whitelist of allowed tables
|
// Whitelist of allowed tables
|
||||||
$allowedTables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE'];
|
$allowedTables = ['data_NPM', 'data_NPM_5channels', 'data_BME280', 'data_envea', 'data_WIND', 'data_MPPT', 'data_NOISE', 'data_MHZ19'];
|
||||||
|
|
||||||
if (!in_array($table, $allowedTables)) {
|
if (!in_array($table, $allowedTables)) {
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
@@ -605,7 +605,8 @@ if ($type == "download_full_table") {
|
|||||||
'data_envea' => 'TimestampUTC,NO2,H2S,NH3,CO,O3,SO2',
|
'data_envea' => 'TimestampUTC,NO2,H2S,NH3,CO,O3,SO2',
|
||||||
'data_WIND' => 'TimestampUTC,Wind_speed_kmh,Wind_direction_V',
|
'data_WIND' => 'TimestampUTC,Wind_speed_kmh,Wind_direction_V',
|
||||||
'data_MPPT' => 'TimestampUTC,Battery_voltage,Battery_current,Solar_voltage,Solar_power,Charger_status',
|
'data_MPPT' => 'TimestampUTC,Battery_voltage,Battery_current,Solar_voltage,Solar_power,Charger_status',
|
||||||
'data_NOISE' => 'TimestampUTC,Current_LEQ,DB_A_value'
|
'data_NOISE' => 'TimestampUTC,Current_LEQ,DB_A_value',
|
||||||
|
'data_MHZ19' => 'TimestampUTC,CO2_ppm'
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -741,6 +742,12 @@ if ($type == "BME280") {
|
|||||||
echo $output;
|
echo $output;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($type == "mhz19") {
|
||||||
|
$command = 'sudo /usr/bin/python3 /var/www/nebuleair_pro_4g/MH-Z19/get_data.py ttyAMA4';
|
||||||
|
$output = shell_exec($command);
|
||||||
|
echo $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if ($type == "table_mesure") {
|
if ($type == "table_mesure") {
|
||||||
$table=$_GET['table'];
|
$table=$_GET['table'];
|
||||||
@@ -1276,6 +1283,10 @@ if ($type == "get_systemd_services") {
|
|||||||
'description' => 'Get Data from noise sensor',
|
'description' => 'Get Data from noise sensor',
|
||||||
'frequency' => 'Every minute'
|
'frequency' => 'Every minute'
|
||||||
],
|
],
|
||||||
|
'nebuleair-mhz19-data.timer' => [
|
||||||
|
'description' => 'Reads CO2 concentration from MH-Z19 sensor',
|
||||||
|
'frequency' => 'Every 2 minutes'
|
||||||
|
],
|
||||||
'nebuleair-db-cleanup-data.timer' => [
|
'nebuleair-db-cleanup-data.timer' => [
|
||||||
'description' => 'Cleans up old data from database',
|
'description' => 'Cleans up old data from database',
|
||||||
'frequency' => 'Daily'
|
'frequency' => 'Daily'
|
||||||
@@ -1341,6 +1352,7 @@ if ($type == "restart_systemd_service") {
|
|||||||
'nebuleair-sara-data.timer',
|
'nebuleair-sara-data.timer',
|
||||||
'nebuleair-bme280-data.timer',
|
'nebuleair-bme280-data.timer',
|
||||||
'nebuleair-mppt-data.timer',
|
'nebuleair-mppt-data.timer',
|
||||||
|
'nebuleair-mhz19-data.timer',
|
||||||
'nebuleair-db-cleanup-data.timer'
|
'nebuleair-db-cleanup-data.timer'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1401,6 +1413,7 @@ if ($type == "toggle_systemd_service") {
|
|||||||
'nebuleair-sara-data.timer',
|
'nebuleair-sara-data.timer',
|
||||||
'nebuleair-bme280-data.timer',
|
'nebuleair-bme280-data.timer',
|
||||||
'nebuleair-mppt-data.timer',
|
'nebuleair-mppt-data.timer',
|
||||||
|
'nebuleair-mhz19-data.timer',
|
||||||
'nebuleair-db-cleanup-data.timer'
|
'nebuleair-db-cleanup-data.timer'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -285,6 +285,36 @@ function getNoise_values(){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getMHZ19_values(){
|
||||||
|
console.log("Data from MH-Z19 CO2 sensor:");
|
||||||
|
$("#loading_mhz19").show();
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'launcher.php?type=mhz19',
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'GET',
|
||||||
|
success: function(response) {
|
||||||
|
console.log(response);
|
||||||
|
const tableBody = document.getElementById("data-table-body_mhz19");
|
||||||
|
tableBody.innerHTML = "";
|
||||||
|
$("#loading_mhz19").hide();
|
||||||
|
|
||||||
|
if (response.CO2 !== undefined) {
|
||||||
|
$("#data-table-body_mhz19").append(`
|
||||||
|
<tr>
|
||||||
|
<td>CO2</td>
|
||||||
|
<td>${response.CO2} ppm</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: function(xhr, status, error) {
|
||||||
|
console.error('AJAX request failed:', status, error);
|
||||||
|
$("#loading_mhz19").hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function getBME280_values(){
|
function getBME280_values(){
|
||||||
console.log("Data from I2C BME280:");
|
console.log("Data from I2C BME280:");
|
||||||
$("#loading_BME280").show();
|
$("#loading_BME280").show();
|
||||||
@@ -462,6 +492,29 @@ error: function(xhr, status, error) {
|
|||||||
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
|
container.innerHTML += i2C_HTML; // Add the I2C card if condition is met
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//creates MH-Z19 CO2 card
|
||||||
|
if (config.MHZ19) {
|
||||||
|
const MHZ19_HTML = `
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Port UART 4
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title">MH-Z19 CO2</h5>
|
||||||
|
<p class="card-text">Capteur de dioxyde de carbone.</p>
|
||||||
|
<button class="btn btn-primary mb-1" onclick="getMHZ19_values()" data-i18n="common.getData">Get Data</button>
|
||||||
|
<div id="loading_mhz19" class="spinner-border spinner-border-sm" style="display: none;" role="status"></div>
|
||||||
|
<table class="table table-striped-columns">
|
||||||
|
<tbody id="data-table-body_mhz19"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
container.innerHTML += MHZ19_HTML;
|
||||||
|
}
|
||||||
|
|
||||||
//Si on a des SONDES ENVEA connectée il faut faire un deuxième call dans la table envea_sondes_table
|
//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
|
//creates ENVEA debug card
|
||||||
if (config.envea) {
|
if (config.envea) {
|
||||||
|
|||||||
@@ -205,6 +205,38 @@ AccuracySec=1s
|
|||||||
WantedBy=timers.target
|
WantedBy=timers.target
|
||||||
EOL
|
EOL
|
||||||
|
|
||||||
|
# Create service and timer files for MH-Z19 CO2 Data
|
||||||
|
cat > /etc/systemd/system/nebuleair-mhz19-data.service << 'EOL'
|
||||||
|
[Unit]
|
||||||
|
Description=NebuleAir MH-Z19 CO2 Data Collection Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/usr/bin/python3 /var/www/nebuleair_pro_4g/MH-Z19/write_data.py
|
||||||
|
User=root
|
||||||
|
WorkingDirectory=/var/www/nebuleair_pro_4g
|
||||||
|
StandardOutput=append:/var/www/nebuleair_pro_4g/logs/mhz19_service.log
|
||||||
|
StandardError=append:/var/www/nebuleair_pro_4g/logs/mhz19_service_errors.log
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOL
|
||||||
|
|
||||||
|
cat > /etc/systemd/system/nebuleair-mhz19-data.timer << 'EOL'
|
||||||
|
[Unit]
|
||||||
|
Description=Run NebuleAir MH-Z19 CO2 Data Collection every 120 seconds
|
||||||
|
Requires=nebuleair-mhz19-data.service
|
||||||
|
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=120s
|
||||||
|
OnUnitActiveSec=120s
|
||||||
|
AccuracySec=1s
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
EOL
|
||||||
|
|
||||||
# Create service and timer files for Database Cleanup
|
# Create service and timer files for Database Cleanup
|
||||||
cat > /etc/systemd/system/nebuleair-db-cleanup-data.service << 'EOL'
|
cat > /etc/systemd/system/nebuleair-db-cleanup-data.service << 'EOL'
|
||||||
[Unit]
|
[Unit]
|
||||||
@@ -294,7 +326,7 @@ 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 mppt db-cleanup noise; do
|
for service in npm envea sara bme280 mppt mhz19 db-cleanup noise; do
|
||||||
systemctl enable nebuleair-$service-data.timer
|
systemctl enable nebuleair-$service-data.timer
|
||||||
systemctl start nebuleair-$service-data.timer
|
systemctl start nebuleair-$service-data.timer
|
||||||
echo "Started nebuleair-$service-data timer"
|
echo "Started nebuleair-$service-data timer"
|
||||||
|
|||||||
@@ -136,6 +136,14 @@ CREATE TABLE IF NOT EXISTS data_NOISE (
|
|||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
# Create a table MHZ19 (CO2 sensor)
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS data_MHZ19 (
|
||||||
|
timestamp TEXT,
|
||||||
|
CO2 REAL
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
# Commit and close the connection
|
# Commit and close the connection
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ timestamp_table
|
|||||||
data_MPPT
|
data_MPPT
|
||||||
data_NOISE
|
data_NOISE
|
||||||
data_WIND
|
data_WIND
|
||||||
|
data_MHZ19
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -124,7 +125,8 @@ def main():
|
|||||||
"data_envea",
|
"data_envea",
|
||||||
"data_WIND",
|
"data_WIND",
|
||||||
"data_MPPT",
|
"data_MPPT",
|
||||||
"data_NOISE"
|
"data_NOISE",
|
||||||
|
"data_MHZ19"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Check which tables actually exist
|
# Check which tables actually exist
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ config_entries = [
|
|||||||
("BME280", "1", "bool"),
|
("BME280", "1", "bool"),
|
||||||
("MPPT", "0", "bool"),
|
("MPPT", "0", "bool"),
|
||||||
("NOISE", "0", "bool"),
|
("NOISE", "0", "bool"),
|
||||||
|
("MHZ19", "0", "bool"),
|
||||||
("modem_version", "XXX", "str"),
|
("modem_version", "XXX", "str"),
|
||||||
("device_type", "nebuleair_pro", "str"),
|
("device_type", "nebuleair_pro", "str"),
|
||||||
("language", "fr", "str"),
|
("language", "fr", "str"),
|
||||||
|
|||||||
Reference in New Issue
Block a user