From 6ad8554c25e845792f6a28370596a0ab5295836a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Wed, 28 Apr 2021 21:14:38 +0200 Subject: [PATCH 1/7] co2_sensor: set measurement interval at last --- ampel-firmware/co2_sensor.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 5795697..e532065 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -80,13 +80,6 @@ namespace sensor { switchState(BOOTUP); - // SCD30 has its own timer. - //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset? - Serial.print(F("Setting SCD30 timestep to ")); - Serial.print(config::measurement_timestep); - Serial.println(" s."); - scd30.setMeasurementInterval(config::measurement_timestep); // [s] - Serial.print(F("Setting temperature offset to -")); Serial.print(abs(config::temperature_offset)); Serial.println(" K."); @@ -100,6 +93,14 @@ namespace sensor { Serial.print(F("Auto-calibration is ")); Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF."); + // SCD30 has its own timer. + //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset? + Serial.println(); + Serial.print(F("Setting SCD30 timestep to ")); + Serial.print(config::measurement_timestep); + Serial.println(" s."); + scd30.setMeasurementInterval(config::measurement_timestep); // [s] + sensor_console::defineIntCommand("co2", setCO2forDebugging, F(" 1500 (Sets co2 level, for debugging purposes)")); sensor_console::defineIntCommand("timer", setTimer, F(" 30 (Sets measurement interval, in s)")); sensor_console::defineCommand("calibrate", startCalibrationProcess, F(" (Starts calibration process)")); -- GitLab From dcba0a6adaf66c6378c96193746f2602504e0719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Sat, 24 Apr 2021 22:15:50 +0200 Subject: [PATCH 2/7] co2_sensor: wait for acclimatization after startup The SCD30 sensor has a response time of 20 s to reach 67 % of its final reading. The first measurement can thus be unreliable, if the environment changed shortly prior to startup. Set measurement interval to the shortest possible value (2 s) and wait then, until the measurements have stabilized. Show a blue led effect in the meanwhile. --- ampel-firmware/co2_sensor.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index e532065..414b45d 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -3,10 +3,12 @@ namespace config { // Values should be defined in config.h uint16_t measurement_timestep = MEASUREMENT_TIMESTEP; // [s] Value between 2 and 1800 (range for SCD30 sensor) + const uint16_t measurement_timestep_bootup = 2; // [s] Measurement timestep during acclimatization const uint16_t altitude_above_sea_level = ALTITUDE_ABOVE_SEA_LEVEL; // [m] uint16_t co2_calibration_level = ATMOSPHERIC_CO2_CONCENTRATION; // [ppm] int8_t max_deviation_during_calibration = 30; // [ppm] int8_t enough_stable_measurements = 60; + const uint8_t max_deviation_during_bootup = 20; // [%] #ifdef TEMPERATURE_OFFSET // Residual heat from CO2 sensor seems to be high enough to change the temperature reading. How much should it be offset? // NOTE: Sign isn't relevant. The returned temperature will always be shifted down. @@ -97,9 +99,9 @@ namespace sensor { //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset? Serial.println(); Serial.print(F("Setting SCD30 timestep to ")); - Serial.print(config::measurement_timestep); - Serial.println(" s."); - scd30.setMeasurementInterval(config::measurement_timestep); // [s] + Serial.print(config::measurement_timestep_bootup); + Serial.println(" s during acclimatization."); + scd30.setMeasurementInterval(config::measurement_timestep_bootup); // [s] sensor_console::defineIntCommand("co2", setCO2forDebugging, F(" 1500 (Sets co2 level, for debugging purposes)")); sensor_console::defineIntCommand("timer", setTimer, F(" 30 (Sets measurement interval, in s)")); @@ -122,6 +124,16 @@ namespace sensor { previous_measurement_at = now; } + bool hasSensorSettled() { + static uint16_t last_co2 = 0; + uint16_t delta; + delta = abs(co2 - last_co2); + last_co2 = co2; + // We assume the sensor has acclimated to the environment if measurements + // change less than a specified percentage of the current value. + return (co2 > 0 && delta < ((uint32_t)co2 * config::max_deviation_during_bootup / 100)); + } + bool countStableMeasurements() { // Returns true, if a sufficient number of stable measurements has been observed. static int16_t previous_co2 = 0; @@ -185,6 +197,15 @@ namespace sensor { } void switchStateForCurrentPPM() { + if (current_state == BOOTUP) { + if (!hasSensorSettled()) return; + switchState(READY); + Serial.println(F("Sensor acclimatization finished.")); + Serial.print(F("Setting SCD30 timestep to ")); + Serial.print(config::measurement_timestep); + Serial.println(" s."); + scd30.setMeasurementInterval(config::measurement_timestep); // [s] + } if (co2 == 0) { // NOTE: Data is available, but it's sometimes erroneous: the sensor outputs // zero ppm but non-zero temperature and non-zero humidity. -- GitLab From 6751d6077a534e64f650c3a91ff054a226d3d756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Sat, 24 Apr 2021 22:21:57 +0200 Subject: [PATCH 3/7] co2_sensor: Add new state INVALID (co2<=0) This state is showed with red waiting LEDs. Previously, the state 'BOOTUP' with blue waiting LEDs was used to show invalid measurements. Use blue waiting LEDs now only during sensor acclimatization. --- ampel-firmware/co2_sensor.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 414b45d..3b2e3c2 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -33,6 +33,7 @@ namespace sensor { * INITIAL -> initial state * BOOTUP -> state after initializing the sensor, i.e. after scd.begin() * READY -> sensor does output valid information (> 0 ppm) and no other condition takes place + * INVALID -> sensor does output invalid CO2 measurements (== 0 ppm) * NEEDS_CALIBRATION -> sensor measurements are too low (< 250 ppm) * PREPARE_CALIBRATION -> forced calibration was initiated, waiting for stable measurements * CALIBRATION -> the sensor does calibrate itself @@ -41,6 +42,7 @@ namespace sensor { INITIAL, BOOTUP, READY, + INVALID, NEEDS_CALIBRATION, PREPARE_CALIBRATION_UNSTABLE, PREPARE_CALIBRATION_STABLE, @@ -50,6 +52,7 @@ namespace sensor { "INITIAL", "BOOTUP", "READY", + "INVALID", "NEEDS_CALIBRATION", "PREPARE_CALIBRATION_UNSTABLE", "PREPARE_CALIBRATION_STABLE", @@ -210,7 +213,7 @@ namespace sensor { // NOTE: Data is available, but it's sometimes erroneous: the sensor outputs // zero ppm but non-zero temperature and non-zero humidity. Serial.println(F("Invalid sensor data - CO2 concentration supposedly 0 ppm")); - switchState(BOOTUP); + switchState(INVALID); } else if ((current_state == PREPARE_CALIBRATION_UNSTABLE) || (current_state == PREPARE_CALIBRATION_STABLE)) { // Check for pre-calibration states first, because we do not want to // leave them before calibration is done. @@ -249,6 +252,9 @@ namespace sensor { case READY: displayCO2OnLedRing(); break; + case INVALID: + led_effects::showWaitingLED(color::red); + break; case NEEDS_CALIBRATION: led_effects::showWaitingLED(color::magenta); break; -- GitLab From fe0234280e1160ce35914d02293182f94fb09e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Wed, 28 Apr 2021 21:15:43 +0200 Subject: [PATCH 4/7] co2_sensor: Reset SCD30 after startup Sometimes after a hard reset of the ESP the SCD30 needs a long time until returning the first measurement. Resetting it after startup seems to fix this behaviour. --- ampel-firmware/co2_sensor.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 3b2e3c2..8756ca0 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -83,6 +83,11 @@ namespace sensor { ESP.restart(); } + // Changes of the SCD30's measurement timestep do not come into effect + // before the next measurement takes place. That means that after a hard reset + // of the ESP the SCD30 sometimes needs a long time until switching back to 2 s + // for acclimatization. Resetting it after startup seems to fix this behaviour. + scd30.reset(); switchState(BOOTUP); Serial.print(F("Setting temperature offset to -")); -- GitLab From c6139baf6af2d3bd36af65a2f6f30edebe88cd7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Thu, 29 Apr 2021 14:00:44 +0200 Subject: [PATCH 5/7] co2_sensor: Drop unused state 'CALIBRATION' --- ampel-firmware/co2_sensor.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 8756ca0..efeec4e 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -36,7 +36,6 @@ namespace sensor { * INVALID -> sensor does output invalid CO2 measurements (== 0 ppm) * NEEDS_CALIBRATION -> sensor measurements are too low (< 250 ppm) * PREPARE_CALIBRATION -> forced calibration was initiated, waiting for stable measurements - * CALIBRATION -> the sensor does calibrate itself */ enum state { INITIAL, @@ -45,8 +44,7 @@ namespace sensor { INVALID, NEEDS_CALIBRATION, PREPARE_CALIBRATION_UNSTABLE, - PREPARE_CALIBRATION_STABLE, - CALIBRATION + PREPARE_CALIBRATION_STABLE }; const char *state_names[] = { "INITIAL", @@ -55,8 +53,7 @@ namespace sensor { "INVALID", "NEEDS_CALIBRATION", "PREPARE_CALIBRATION_UNSTABLE", - "PREPARE_CALIBRATION_STABLE", - "CALIBRATION" }; + "PREPARE_CALIBRATION_STABLE" }; state current_state = INITIAL; void switchState(state); @@ -172,7 +169,6 @@ namespace sensor { } void calibrateAndRestart() { - switchState(CALIBRATION); Serial.print(F("Calibrating SCD30 now...")); scd30.setAltitudeCompensation(config::altitude_above_sea_level); scd30.setForcedRecalibrationFactor(config::co2_calibration_level); @@ -269,8 +265,6 @@ namespace sensor { case PREPARE_CALIBRATION_STABLE: led_effects::showWaitingLED(color::green); break; - case CALIBRATION: // Nothing to do, will restart soon. - break; default: Serial.println(F("Encountered unknown sensor state")); // This should not happen. } -- GitLab From 239daba63ae2c65a444c6cb32118ecc6f4f4c7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Fri, 30 Apr 2021 00:09:21 +0200 Subject: [PATCH 6/7] co2_sensor: Do not report new data in all cases Log every measurement to the serial console, but return only `true` in `processData()`(thus starting further processing like CSV, MQTT, LORAWAN) if the data is reliable (stable measurements, CO2 > 0) or the sensor measures too low CO2 values (< 250). The latter condition should be reported, because the user can then initiate a manual calibration procedure. --- ampel-firmware/co2_sensor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index efeec4e..637ec0a 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -291,7 +291,9 @@ namespace sensor { showState(); - return freshData; + // Report data for further processing only if the data is reliable + // (state 'READY') or manual calibration is necessary (state 'NEEDS_CALIBRATION'). + return freshData && (current_state == READY || current_state == NEEDS_CALIBRATION); } /***************************************************************** -- GitLab From 186bf9f9e1643c60db9894a7ea575ee012bba744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20K=C3=A4ppler?= Date: Fri, 30 Apr 2021 10:21:01 +0200 Subject: [PATCH 7/7] co2_sensor: Remove state 'INVALID' Ongoing investigation showed that the sensor does report 0 ppm only after startup, i.e. in 'BOOTUP' state. We check for this condition in `hasSensprSettled()` already, so there is no need for this state anymore. --- ampel-firmware/co2_sensor.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 637ec0a..a4caca6 100644 --- a/ampel-firmware/co2_sensor.cpp +++ b/ampel-firmware/co2_sensor.cpp @@ -33,7 +33,6 @@ namespace sensor { * INITIAL -> initial state * BOOTUP -> state after initializing the sensor, i.e. after scd.begin() * READY -> sensor does output valid information (> 0 ppm) and no other condition takes place - * INVALID -> sensor does output invalid CO2 measurements (== 0 ppm) * NEEDS_CALIBRATION -> sensor measurements are too low (< 250 ppm) * PREPARE_CALIBRATION -> forced calibration was initiated, waiting for stable measurements */ @@ -41,7 +40,6 @@ namespace sensor { INITIAL, BOOTUP, READY, - INVALID, NEEDS_CALIBRATION, PREPARE_CALIBRATION_UNSTABLE, PREPARE_CALIBRATION_STABLE @@ -50,7 +48,6 @@ namespace sensor { "INITIAL", "BOOTUP", "READY", - "INVALID", "NEEDS_CALIBRATION", "PREPARE_CALIBRATION_UNSTABLE", "PREPARE_CALIBRATION_STABLE" }; @@ -210,12 +207,7 @@ namespace sensor { Serial.println(" s."); scd30.setMeasurementInterval(config::measurement_timestep); // [s] } - if (co2 == 0) { - // NOTE: Data is available, but it's sometimes erroneous: the sensor outputs - // zero ppm but non-zero temperature and non-zero humidity. - Serial.println(F("Invalid sensor data - CO2 concentration supposedly 0 ppm")); - switchState(INVALID); - } else if ((current_state == PREPARE_CALIBRATION_UNSTABLE) || (current_state == PREPARE_CALIBRATION_STABLE)) { + if ((current_state == PREPARE_CALIBRATION_UNSTABLE) || (current_state == PREPARE_CALIBRATION_STABLE)) { // Check for pre-calibration states first, because we do not want to // leave them before calibration is done. bool ready_for_calibration = countStableMeasurements(); @@ -253,9 +245,6 @@ namespace sensor { case READY: displayCO2OnLedRing(); break; - case INVALID: - led_effects::showWaitingLED(color::red); - break; case NEEDS_CALIBRATION: led_effects::showWaitingLED(color::magenta); break; -- GitLab