Commits (5)
  • Käppler's avatar
    co2_sensor: wait for acclimatization after startup · dcba0a6a
    Käppler authored
    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.
    dcba0a6a
  • Käppler's avatar
    co2_sensor: Add new state INVALID (co2<=0) · 6751d607
    Käppler authored
    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.
    6751d607
  • Käppler's avatar
    co2_sensor: Reset SCD30 after startup · a412abe2
    Käppler authored
    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.
    a412abe2
  • Käppler's avatar
    co2_sensor: Drop unused state 'CALIBRATION' · 67578525
    Käppler authored
    67578525
  • Käppler's avatar
    co2_sensor: Do not report new data in all cases · c91a8491
    Käppler authored
    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.
    c91a8491
......@@ -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.
......@@ -31,27 +33,27 @@ 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
*/
enum state {
INITIAL,
BOOTUP,
READY,
INVALID,
NEEDS_CALIBRATION,
PREPARE_CALIBRATION_UNSTABLE,
PREPARE_CALIBRATION_STABLE,
CALIBRATION
PREPARE_CALIBRATION_STABLE
};
const char *state_names[] = {
"INITIAL",
"BOOTUP",
"READY",
"INVALID",
"NEEDS_CALIBRATION",
"PREPARE_CALIBRATION_UNSTABLE",
"PREPARE_CALIBRATION_STABLE",
"CALIBRATION" };
"PREPARE_CALIBRATION_STABLE" };
state current_state = INITIAL;
void switchState(state);
......@@ -78,6 +80,10 @@ namespace sensor {
ESP.restart();
}
// 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.
scd30.reset();
switchState(BOOTUP);
Serial.print(F("Setting temperature offset to -"));
......@@ -97,9 +103,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 +128,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;
......@@ -152,7 +168,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);
......@@ -185,11 +200,20 @@ 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.
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.
......@@ -228,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;
......@@ -237,8 +264,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,8 +289,10 @@ 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);
}
/*****************************************************************
......