co2_sensor.cpp 6.47 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
}

namespace sensor {
  SCD30 scd30;
  int16_t co2 = 0;
  float temperature = 0;
  float humidity = 0;
  String timestamp = "";
24
25
26
  int16_t stable_measurements = 0;
  uint32_t waiting_color = color::blue;
  bool should_calibrate = false;
27
28
29
30
31
32

  void initialize() {
#if defined(ESP8266)
    Wire.begin(12, 14);  // ESP8266 - D6, D5;
#endif
#if defined(ESP32)
Eric Duminil's avatar
Eric Duminil committed
33
    Wire.begin(21, 22); // ESP32
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
    /**
     *  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
52
    //NOTE: The timer seems to be inaccurate, though, possibly depending on voltage. Should it be offset?
53
54
55
56
    Serial.println();
    Serial.print(F("Setting SCD30 timestep to "));
    Serial.print(config::measurement_timestep);
    Serial.println(" s.");
Eric Duminil's avatar
Eric Duminil committed
57
    scd30.setMeasurementInterval(config::measurement_timestep); // [s]
58

59
    Serial.print(F("Setting temperature offset to -"));
60
61
62
    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.
63
    delay(100);
64

65
    Serial.print(F("Temperature offset is : -"));
66
67
68
    Serial.print(scd30.getTemperatureOffset());
    Serial.println(" K");

69
    Serial.print(F("Auto-calibration is "));
70
71
72
    Serial.println(config::auto_calibrate_sensor ? "ON." : "OFF.");
  }

Eric Duminil's avatar
Eric Duminil committed
73
  //NOTE: should timer deviation be used to adjust measurement_timestep?
Eric Duminil's avatar
Eric Duminil committed
74
  void checkTimerDeviation() {
75
    static int32_t previous_measurement_at = 0;
Eric Duminil's avatar
Eric Duminil committed
76
77
78
79
80
81
82
    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;
  }

83
  void countStableMeasurements() {
Eric Duminil's avatar
Eric Duminil committed
84
    static int16_t previous_co2 = 0;
85
86
    if (co2 > (previous_co2 - 30) && co2 < (previous_co2 + 30)) {
      stable_measurements++;
Eric Duminil's avatar
Eric Duminil committed
87
      Serial.print(F("Number of stable measurements : "));
Eric Duminil's avatar
Eric Duminil committed
88
      Serial.println(stable_measurements);
Eric Duminil's avatar
Notes    
Eric Duminil committed
89
      //NOTE: Hue could change from blue or red to green over 60 measurements.
90
91
92
93
94
      waiting_color = color::green;
    } else {
      stable_measurements = 0;
      waiting_color = color::red;
    }
Eric Duminil's avatar
Eric Duminil committed
95
    previous_co2 = co2;
96
97
98
  }

  bool updateDataIfAvailable() {
Eric Duminil's avatar
Eric Duminil committed
99
    if (scd30.dataAvailable()) {
Eric Duminil's avatar
Eric Duminil committed
100
      // checkTimerDeviation();
101
      timestamp = ntp::getLocalTime();
Eric Duminil's avatar
Eric Duminil committed
102
103
104
105
106
107
108
109
      co2 = scd30.getCO2();
      temperature = scd30.getTemperature();
      humidity = scd30.getHumidity();
      return true;
    }
    return false;
  }

110
  void startCalibrationProcess() {
111
112
113
114
    /** 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.
     */
115
116
117
118
    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."));
119
    should_calibrate = true;
120
121
  }

122
123
  void calibrateAndRestart() {
    Serial.print(F("Calibrating SCD30 now..."));
124
125
    scd30.setAltitudeCompensation(config::altitude_above_sea_level);
    scd30.setForcedRecalibrationFactor(config::co2_calibration_level);
126
127
    Serial.println(F(" Done!"));
    Serial.println(F("Sensor calibrated."));
Eric Duminil's avatar
Eric Duminil committed
128
    resetAmpel();
129
  }
130

131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
  void logToSerial() {
    Serial.println(timestamp);
    Serial.print(F("co2(ppm): "));
    Serial.print(co2);
    Serial.print(F(" temp(C): "));
    Serial.print(temperature);
    Serial.print(F(" humidity(%): "));
    Serial.println(humidity);
  }

  void displayCO2OnLedRing() {
    if (co2 < 250) {
      // Sensor should be calibrated.
      LedEffects::showWaitingLED(color::magenta);
      return;
    }
    /**
     * Display data, even if it's "old" (with breathing).
     * Those effects include a short delay.
     */
    if (co2 < 2000) {
      LedEffects::displayCO2color(co2);
      LedEffects::breathe(co2);
    } else {
      // >= 2000: entire ring blinks red
      LedEffects::redAlert();
    }
  }

  void processData() {
Eric Duminil's avatar
Eric Duminil committed
161
    bool freshData = updateDataIfAvailable();
162
163

    //NOTE: Data is available, but it's sometimes erroneous: the sensor outputs zero ppm but non-zero temperature and non-zero humidity.
Eric Duminil's avatar
Eric Duminil committed
164
    if (co2 <= 0) {
165
166
167
168
169
170
      // No measurement yet. Waiting.
      LedEffects::showWaitingLED(color::blue);
      return;
    }

    /**
171
     * Fresh data. Log it and send it if needed.
172
173
     */
    if (freshData) {
Eric Duminil's avatar
Eric Duminil committed
174
175
176
      if (should_calibrate) {
        countStableMeasurements();
      }
177

178
      logToSerial();
Eric Duminil's avatar
Eric Duminil committed
179
180

      //TODO: Move the 3 back to ampel-firmware.ino and remove headers from co2_sensor.h
181
182

#ifdef CSV_WRITER
Eric Duminil's avatar
Eric Duminil committed
183
      csv_writer::logIfTimeHasCome(timestamp, co2, temperature, humidity);
184
#endif
185
186

#ifdef MQTT
Eric Duminil's avatar
Eric Duminil committed
187
      mqtt::publishIfTimeHasCome(timestamp, co2, temperature, humidity);
188
#endif
Eric Duminil's avatar
Eric Duminil committed
189

190
#if defined(LORAWAN) && defined(ESP32)
Eric Duminil's avatar
Eric Duminil committed
191
192
      lorawan::preparePayloadIfTimehasCome();
#endif
193
194
195
196
197
198
199
200
201
202
    }

    if (should_calibrate) {
      if (stable_measurements == 60) {
        calibrateAndRestart();
      }
      LedEffects::showWaitingLED(waiting_color);
      return;
    }

203
    displayCO2OnLedRing();
204
  }
205
}