co2_sensor.cpp 5.36 KB
Newer Older
1
2
3
4
#include "co2_sensor.h"

namespace config {
  // Values should be defined in config.h
Eric Duminil's avatar
Eric Duminil committed
5
6
7
  uint16_t measurement_timestep = MEASUREMENT_TIMESTEP; // [s] Value between 2 and 1800 (range for SCD30 sensor)
  const uint16_t altitude_above_sea_level = ALTITUDE_ABOVE_SEA_LEVEL; // [m]
  uint16_t co2_calibration_level = ATMOSPHERIC_CO2_CONCENTRATION; // [ppm]
8
9
10
#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.
Eric Duminil's avatar
Eric Duminil committed
11
  const float temperature_offset = TEMPERATURE_OFFSET; // [K]
12
13
14
#else
  const float temperature_offset = -3.0;  // [K] Temperature measured by sensor is usually at least 3K too high.
#endif
Eric Duminil's avatar
Eric Duminil committed
15
  const bool auto_calibrate_sensor = AUTO_CALIBRATE_SENSOR; // [true / false]
16
17
18
19
20
21
22
23
24
25
26
27
28
29
}

namespace sensor {
  SCD30 scd30;
  int16_t co2 = 0;
  float temperature = 0;
  float humidity = 0;
  String timestamp = "";

  void initialize() {
#if defined(ESP8266)
    Wire.begin(12, 14);  // ESP8266 - D6, D5;
#endif
#if defined(ESP32)
Eric Duminil's avatar
Eric Duminil committed
30
    Wire.begin(21, 22); // ESP32
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    /**
     *  SCD30   ESP32
     *  VCC --- 3V3
     *  GND --- GND
     *  SCL --- SCL (GPIO22) //NOTE: GPIO3 Would be more convenient (right next to GND)
     *  SDA --- SDA (GPIO21) //NOTE: GPIO1 would be more convenient (right next to GPO3)
     */
#endif

    // CO2
    if (scd30.begin(config::auto_calibrate_sensor) == false) {
      Serial.println("Air sensor not detected. Please check wiring. Freezing...");
      while (1) {
        LedEffects::showWaitingLED(color::red);
      }
    }

    // SCD30 has its own timer.
Eric Duminil's avatar
Eric Duminil committed
49
    //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset?
50
51
52
53
    Serial.println();
    Serial.print(F("Setting SCD30 timestep to "));
    Serial.print(config::measurement_timestep);
    Serial.println(" s.");
Eric Duminil's avatar
Eric Duminil committed
54
    scd30.setMeasurementInterval(config::measurement_timestep); // [s]
55

56
    Serial.print(F("Setting temperature offset to -"));
57
58
59
    Serial.print(abs(config::temperature_offset));
    Serial.println(" K.");
    scd30.setTemperatureOffset(abs(config::temperature_offset)); // setTemperatureOffset only accepts positive numbers, but shifts the temperature down.
60
    delay(100);
61

62
    Serial.print(F("Temperature offset is : -"));
63
64
65
    Serial.print(scd30.getTemperatureOffset());
    Serial.println(" K");

66
    Serial.print(F("Auto-calibration is "));
67
68
69
    Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF.");
  }

Eric Duminil's avatar
Eric Duminil committed
70
71
72
73
74
75
76
77
78
79
  //NOTE: should time offset be used to reset measurement_timestep?
  void checkTimerDeviation() {
    static int32_t previous_measurement_at = 0; // Used to monitor sensor timer offset.
    int32_t now = millis();
    Serial.print("Measurement time offset : ");
    Serial.print(now - previous_measurement_at - config::measurement_timestep * 1000);
    Serial.println(" ms.");
    previous_measurement_at = now;
  }

Eric Duminil's avatar
Eric Duminil committed
80
81
  bool updateDataIfAvailable() {
    //TODO: Save count of stable measurements
Eric Duminil's avatar
Eric Duminil committed
82
    static int16_t previous_co2 = 0;
Eric Duminil's avatar
Eric Duminil committed
83
    if (scd30.dataAvailable()) {
Eric Duminil's avatar
Eric Duminil committed
84
      // checkTimerDeviation();
Eric Duminil's avatar
Eric Duminil committed
85
86
87
88
89
90
91
92
      co2 = scd30.getCO2();
      temperature = scd30.getTemperature();
      humidity = scd30.getHumidity();
      return true;
    }
    return false;
  }

93
94
  void waitUntilMeasurementsAreStable() {
    //TODO: Refactor completely, in order to avoid very long loop?
95
96
97
98
    /** From the sensor documentation:
     * For best results, the sensor has to be run in a stable environment in continuous mode at
     * a measurement rate of 2s for at least two minutes before applying the FRC command and sending the reference value.
     */
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
    Serial.println(F("Setting SCD30 timestep to 2s, prior to calibration."));
    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("It could take a very long time."));

    //########################################################################################################
    // (c) Mario Lukas
    // https://github.com/mariolukas/Watterott-CO2-Ampel-Plus-Firmware/blob/main/CO2-Ampel_Plus/Sensor.cpp#L57
    uint32_t last_color = color::blue;
    int stable_measurements = 0, last_co2 = 0;
    for (stable_measurements = 0; stable_measurements < 60;) {
      if (scd30.dataAvailable()) {
        co2 = scd30.getCO2();
        //No more than +/-30ppm variation compared to previous measurement.
        if ((co2 > (last_co2 - 30)) && (co2 < (last_co2 + 30))) {
          last_color = color::green;
          stable_measurements++;
        } else {
          last_color = color::red;
          stable_measurements = 0;
        }
        last_co2 = co2;
      }
      LedEffects::showKITTWheel(last_color, 1);
    }
    //########################################################################################################
  }

  void startCalibrationProcess() {
    waitUntilMeasurementsAreStable();
129
130
131
132
133
134
135
    Serial.print("Starting SCD30 calibration...");
    scd30.setAltitudeCompensation(config::altitude_above_sea_level);
    scd30.setForcedRecalibrationFactor(config::co2_calibration_level);
    Serial.println(" Done!");
    Serial.println("Sensor calibrated.");
    Serial.println("Sensor will now restart.");
    LedEffects::showKITTWheel(color::green, 5);
Eric Duminil's avatar
TODOs    
Eric Duminil committed
136
    //TODO: Add LEDs off and move to util::reset()
137
138
139
140
    FS_LIB.end();
    ESP.restart();
  }
}