Commit 4d2f0f0c authored by Eric Duminil's avatar Eric Duminil
Browse files

Merge branch 'feature/sensor_states' into develop

parents bdee5cb3 1435e91a
...@@ -15,6 +15,7 @@ namespace config { ...@@ -15,6 +15,7 @@ namespace config {
const float temperature_offset = -3.0; // [K] Temperature measured by sensor is usually at least 3K too high. const float temperature_offset = -3.0; // [K] Temperature measured by sensor is usually at least 3K too high.
#endif #endif
bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false] bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false]
const bool debug_sensor_states = false; // If true, log state transitions over serial console
} }
namespace sensor { namespace sensor {
...@@ -24,12 +25,39 @@ namespace sensor { ...@@ -24,12 +25,39 @@ namespace sensor {
float humidity = 0; float humidity = 0;
char timestamp[23]; char timestamp[23];
int16_t stable_measurements = 0; int16_t stable_measurements = 0;
uint32_t waiting_color = color::blue;
bool should_calibrate = false; /**
* Define sensor states
* 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
* 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,
NEEDS_CALIBRATION,
PREPARE_CALIBRATION_UNSTABLE,
PREPARE_CALIBRATION_STABLE,
CALIBRATION
};
const char *state_names[] = {
"INITIAL",
"BOOTUP",
"READY",
"NEEDS_CALIBRATION",
"PREPARE_CALIBRATION_UNSTABLE",
"PREPARE_CALIBRATION_STABLE",
"CALIBRATION" };
state current_state = INITIAL;
void switchState(state);
void initialize() { void initialize() {
#if defined(ESP8266) #if defined(ESP8266)
Wire.begin(12, 14); // ESP8266 - D6, D5; Wire.begin(12, 14); // ESP8266 - D6, D5;
#endif #endif
#if defined(ESP32) #if defined(ESP32)
Wire.begin(21, 22); // ESP32 Wire.begin(21, 22); // ESP32
...@@ -49,6 +77,8 @@ namespace sensor { ...@@ -49,6 +77,8 @@ namespace sensor {
ESP.restart(); ESP.restart();
} }
switchState(BOOTUP);
// SCD30 has its own timer. // SCD30 has its own timer.
//NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset? //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset?
Serial.println(); Serial.println();
...@@ -91,19 +121,21 @@ namespace sensor { ...@@ -91,19 +121,21 @@ namespace sensor {
previous_measurement_at = now; previous_measurement_at = now;
} }
void countStableMeasurements() { bool countStableMeasurements() {
// Returns true, if a sufficient number of stable measurements has been observed.
static int16_t previous_co2 = 0; static int16_t previous_co2 = 0;
if (co2 > (previous_co2 - config::max_deviation_during_calibration) if (co2 > (previous_co2 - config::max_deviation_during_calibration)
&& co2 < (previous_co2 + config::max_deviation_during_calibration)) { && co2 < (previous_co2 + config::max_deviation_during_calibration)) {
stable_measurements++; stable_measurements++;
Serial.print(F("Number of stable measurements : ")); Serial.print(F("Number of stable measurements : "));
Serial.println(stable_measurements); Serial.println(stable_measurements);
waiting_color = color::green; switchState(PREPARE_CALIBRATION_STABLE);
} else { } else {
stable_measurements = 0; stable_measurements = 0;
waiting_color = color::red; switchState(PREPARE_CALIBRATION_UNSTABLE);
} }
previous_co2 = co2; previous_co2 = co2;
return (stable_measurements == config::enough_stable_measurements);
} }
void startCalibrationProcess() { void startCalibrationProcess() {
...@@ -115,10 +147,11 @@ namespace sensor { ...@@ -115,10 +147,11 @@ namespace sensor {
scd30.setMeasurementInterval(2); // [s] The change will only take effect after next measurement. scd30.setMeasurementInterval(2); // [s] The change will only take effect after next measurement.
Serial.println(F("Waiting until the measurements are stable for at least 2 minutes.")); Serial.println(F("Waiting until the measurements are stable for at least 2 minutes."));
Serial.println(F("It could take a very long time.")); Serial.println(F("It could take a very long time."));
should_calibrate = true; switchState(PREPARE_CALIBRATION_UNSTABLE);
} }
void calibrateAndRestart() { void calibrateAndRestart() {
switchState(CALIBRATION);
Serial.print(F("Calibrating SCD30 now...")); Serial.print(F("Calibrating SCD30 now..."));
scd30.setAltitudeCompensation(config::altitude_above_sea_level); scd30.setAltitudeCompensation(config::altitude_above_sea_level);
scd30.setForcedRecalibrationFactor(config::co2_calibration_level); scd30.setForcedRecalibrationFactor(config::co2_calibration_level);
...@@ -137,12 +170,41 @@ namespace sensor { ...@@ -137,12 +170,41 @@ namespace sensor {
Serial.println(humidity, 1); Serial.println(humidity, 1);
} }
void displayCO2OnLedRing() { void switchState(state new_state) {
if (co2 < 250) { if (new_state == current_state) {
// Sensor should be calibrated.
led_effects::showWaitingLED(color::magenta);
return; return;
} }
if (config::debug_sensor_states) {
Serial.print(F("Changing sensor state: "));
Serial.print(state_names[current_state]);
Serial.print(" -> ");
Serial.println(state_names[new_state]);
}
current_state = new_state;
}
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)) {
// Check for pre-calibration states first, because we do not want to
// leave them before calibration is done.
bool ready_for_calibration = countStableMeasurements();
if (ready_for_calibration) {
calibrateAndRestart();
}
} else if (co2 < 250) {
// Sensor should be calibrated.
switchState(NEEDS_CALIBRATION);
} else {
switchState(READY);
}
}
void displayCO2OnLedRing() {
/** /**
* Display data, even if it's "old" (with breathing). * Display data, even if it's "old" (with breathing).
* A short delay is required in order to let background tasks run on the ESP8266. * A short delay is required in order to let background tasks run on the ESP8266.
...@@ -157,6 +219,30 @@ namespace sensor { ...@@ -157,6 +219,30 @@ namespace sensor {
} }
} }
void showState() {
switch (current_state) {
case BOOTUP:
led_effects::showWaitingLED(color::blue);
break;
case READY:
displayCO2OnLedRing();
break;
case NEEDS_CALIBRATION:
led_effects::showWaitingLED(color::magenta);
break;
case PREPARE_CALIBRATION_UNSTABLE:
led_effects::showWaitingLED(color::red);
break;
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.
}
}
/** Gets fresh data if available, checks calibration status, displays CO2 levels. /** Gets fresh data if available, checks calibration status, displays CO2 levels.
* Returns true if fresh data is available, for further processing (e.g. MQTT, CSV or LoRa) * Returns true if fresh data is available, for further processing (e.g. MQTT, CSV or LoRa)
*/ */
...@@ -169,34 +255,15 @@ namespace sensor { ...@@ -169,34 +255,15 @@ namespace sensor {
co2 = scd30.getCO2(); co2 = scd30.getCO2();
temperature = scd30.getTemperature(); temperature = scd30.getTemperature();
humidity = scd30.getHumidity(); humidity = scd30.getHumidity();
}
//NOTE: Data is available, but it's sometimes erroneous: the sensor outputs zero ppm but non-zero temperature and non-zero humidity. switchStateForCurrentPPM();
if (co2 <= 0) {
// No measurement yet. Waiting.
led_effects::showWaitingLED(color::blue);
return false;
}
/** // Log every time fresh data is available.
* Fresh data. Log it and send it if needed.
*/
if (freshData) {
if (should_calibrate) {
countStableMeasurements();
}
logToSerial(); logToSerial();
} }
if (should_calibrate) { showState();
if (stable_measurements == config::enough_stable_measurements) {
calibrateAndRestart();
}
led_effects::showWaitingLED(waiting_color);
return false;
}
displayCO2OnLedRing();
return freshData; return freshData;
} }
...@@ -207,6 +274,7 @@ namespace sensor { ...@@ -207,6 +274,7 @@ namespace sensor {
Serial.print(F("DEBUG. Setting CO2 to ")); Serial.print(F("DEBUG. Setting CO2 to "));
co2 = fakeCo2; co2 = fakeCo2;
Serial.println(co2); Serial.println(co2);
switchStateForCurrentPPM();
} }
void setAutoCalibration(int32_t autoCalibration) { void setAutoCalibration(int32_t autoCalibration) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment