diff --git a/ampel-firmware/co2_sensor.cpp b/ampel-firmware/co2_sensor.cpp index 5795697b05eb325ae2f4e8822f5f994bcffa043a..a4caca6e3c16827f67950a1992d6c22bff8be8a5 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. @@ -33,7 +35,6 @@ namespace sensor { * READY -> sensor does output valid information (> 0 ppm) and no other condition takes place * 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, @@ -41,8 +42,7 @@ namespace sensor { READY, NEEDS_CALIBRATION, PREPARE_CALIBRATION_UNSTABLE, - PREPARE_CALIBRATION_STABLE, - CALIBRATION + PREPARE_CALIBRATION_STABLE }; const char *state_names[] = { "INITIAL", @@ -50,8 +50,7 @@ namespace sensor { "READY", "NEEDS_CALIBRATION", "PREPARE_CALIBRATION_UNSTABLE", - "PREPARE_CALIBRATION_STABLE", - "CALIBRATION" }; + "PREPARE_CALIBRATION_STABLE" }; state current_state = INITIAL; void switchState(state); @@ -78,15 +77,13 @@ 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); - // 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 +97,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_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)")); sensor_console::defineCommand("calibrate", startCalibrationProcess, F(" (Starts calibration process)")); @@ -121,6 +126,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; @@ -151,7 +166,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); @@ -184,12 +198,16 @@ namespace sensor { } void switchStateForCurrentPPM() { - 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(BOOTUP); - } else if ((current_state == PREPARE_CALIBRATION_UNSTABLE) || (current_state == PREPARE_CALIBRATION_STABLE)) { + 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 ((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(); @@ -236,8 +254,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. } @@ -264,7 +280,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); } /*****************************************************************