├── README.md ├── P1_CO2_plexi_v10.eps ├── CO2-Ampel-DIY-Octopus-SCD30.ino ├── co2_logger_octi_scd30.ino ├── co2_logger_rtc.ino ├── CO2-Ampel-DIY-Octopus-SCD30-OLED.ino ├── CO2-Ampel-DIY-Octopus-SCD30-OLEDflckerfree.ino ├── CO2-Ampel-DIY-Octopus-SCD30-Neo-OLEDflickerfree.ino ├── CO2-Ampel-DIY-Octopus-SCD30-WEB-AP-CAL.ino ├── QT_RP2040_CO2_NEO_BME_LCD_RING.ino ├── co2_epaper_webserver.ino ├── CO2-Ampel-DIY-Octopus-SCD30.abp ├── co2_esp8266_feather_scd30_neo_web.abp └── CO2-Ampel-DIY-Octopus-SCD30-WEB-AP-CAL.abp /README.md: -------------------------------------------------------------------------------- 1 | # CO2-Ampel 2 | -------------------------------------------------------------------------------- /P1_CO2_plexi_v10.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/make-IoT/CO2-Ampel/HEAD/P1_CO2_plexi_v10.eps -------------------------------------------------------------------------------- /CO2-Ampel-DIY-Octopus-SCD30.ino: -------------------------------------------------------------------------------- 1 | /* This program is distributed in the hope that it will be useful, 2 | but WITHOUT ANY WARRANTY; without even the implied warranty of 3 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4 | GNU General Public License for more details. */ 5 | #include 6 | #include 7 | #include 8 | 9 | int CO2 = 0 ; 10 | //Reading CO2, humidity and temperature from the SCD30 By: Nathan Seidle SparkFun Electronics 11 | 12 | //https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library 13 | 14 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 15 | 16 | void setup(){ // Einmalige Initialisierung 17 | WiFi.forceSleepBegin(); // Wifi off 18 | pinMode( 12 , OUTPUT); 19 | pinMode( 13 , OUTPUT); 20 | pinMode( 14 , OUTPUT); 21 | Wire.begin(); // ---- Initialisiere den I2C-Bus 22 | 23 | if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C"); 24 | 25 | if (airSensorSCD30.begin() == false) { 26 | Serial.println("The SCD30 did not respond. Please check wiring."); 27 | while(1) { 28 | yield(); 29 | delay(1); 30 | } 31 | } 32 | 33 | airSensorSCD30.setAutoSelfCalibration(false); // Sensirion no auto calibration 34 | 35 | airSensorSCD30.setMeasurementInterval(2); // CO2-Messung alle 5 s 36 | 37 | Serial.begin(115200); 38 | Serial.println(); 39 | digitalWrite( 12 , LOW ); 40 | 41 | digitalWrite( 13 , LOW ); 42 | 43 | digitalWrite( 14 , LOW ); 44 | 45 | Wire.setClock(100000L); // 100 kHz SCD30 46 | Wire.setClockStretchLimit(200000L);// CO2-SCD30 47 | } 48 | 49 | void loop() { // Kontinuierliche Wiederholung 50 | CO2 = airSensorSCD30.getCO2() ; 51 | Serial.print("CO2:"+String(String(CO2))); 52 | Serial.println(); 53 | if (( ( CO2 ) < ( 1000 ) )) 54 | { 55 | digitalWrite( 12 , LOW ); 56 | digitalWrite( 13 , LOW ); 57 | digitalWrite( 14 , HIGH ); 58 | Serial.print("green"); 59 | Serial.println(); 60 | } 61 | else 62 | { 63 | if (( ( CO2 ) < ( 1400 ) )) 64 | { 65 | digitalWrite( 12 , LOW ); 66 | digitalWrite( 13 , HIGH ); 67 | digitalWrite( 14 , LOW ); 68 | Serial.print("yellow"); 69 | Serial.println(); 70 | } 71 | else 72 | { 73 | digitalWrite( 12 , HIGH ); 74 | digitalWrite( 13 , LOW ); 75 | digitalWrite( 14 , LOW ); 76 | Serial.print("red"); 77 | Serial.println(); 78 | } 79 | } 80 | delay( 2000 ); 81 | } 82 | -------------------------------------------------------------------------------- /co2_logger_octi_scd30.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Set the pins used 8 | #define cardSelect 15 9 | 10 | File logfile; 11 | 12 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 13 | 14 | // blink out an error code 15 | void error(uint8_t errno) { 16 | while(1) { 17 | uint8_t i; 18 | for (i=0; i 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | Adafruit_SH110X display = Adafruit_SH110X(64, 128, &Wire); 17 | GFXcanvas1 canvas(64,128); 18 | 19 | unsigned long myTimer = 0; 20 | unsigned long myTimeout = 60000; // heart beat 60sec, LED build-in 21 | 22 | // OLED FeatherWing buttons map to different pins depending on board: 23 | #define BUTTON_A 0 24 | #define BUTTON_B 16 25 | #define BUTTON_C 2 26 | 27 | int CO2 = 0 ; 28 | //Reading CO2, humidity and temperature from the SCD30 By: Nathan Seidle SparkFun Electronics 29 | 30 | //https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library 31 | 32 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 33 | String matrixausgabe_text = " "; // Ausgabetext als globale Variable 34 | 35 | volatile int matrixausgabe_index = 0;// aktuelle Position in Matrix 36 | 37 | volatile int matrixausgabe_wait = 0;// warte bis Anzeige durchgelaufen ist 38 | 39 | //--------------------------------------- Charlieplex Matrix 40 | Adafruit_IS31FL3731_Wing matrix = Adafruit_IS31FL3731_Wing(); 41 | 42 | Ticker matrixausgabe; 43 | void matrixUpdate(){ // Update Charlieplexmatrix über Ticker 44 | int anzahlPixel=(matrixausgabe_text.length())*6; 45 | if (anzahlPixel > 15) { // Scrollen 46 | if (matrixausgabe_index >= -anzahlPixel) { 47 | matrix.clear(); 48 | matrix.setCursor(matrixausgabe_index,0); 49 | matrix.print(matrixausgabe_text); 50 | matrixausgabe_index--; 51 | } 52 | else { 53 | matrixausgabe_index = 12; 54 | matrixausgabe_wait=0; 55 | } 56 | } 57 | else {// nur 3 Zeichen lang-> kein Scroll 58 | matrix.setCursor(0,0); 59 | matrix.print(matrixausgabe_text); 60 | matrixausgabe_wait = 0; // no wait 61 | } 62 | } 63 | void matrixAnzeige(String text, int wait) { // Setze Ausgabetext 64 | if (matrixausgabe_text != text) { // update nur bei neuem Text 65 | matrix.clear(); 66 | matrixausgabe_index = 0; 67 | matrixausgabe_text = text; 68 | matrixausgabe_wait = wait; 69 | while (matrixausgabe_wait) delay(10); // warte bis Text einmal ausgegeben ist 70 | } 71 | }; 72 | 73 | Adafruit_NeoPixel WSpixels = Adafruit_NeoPixel((1<24)?1:24,15,NEO_RGB + NEO_KHZ800); 74 | 75 | void setup(){ // Einmalige Initialisierung 76 | WiFi.forceSleepBegin(); // Wifi off 77 | pinMode( 12 , OUTPUT); 78 | pinMode( 13 , OUTPUT); 79 | pinMode( 14 , OUTPUT); 80 | Wire.begin(); // ---- Initialisiere den I2C-Bus 81 | 82 | if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C"); 83 | 84 | if (airSensorSCD30.begin() == false) { 85 | Serial.println("The SCD30 did not respond. Please check wiring."); 86 | while(1) { 87 | yield(); 88 | delay(1); 89 | } 90 | } 91 | 92 | airSensorSCD30.setAutoSelfCalibration(false); // Sensirion no auto calibration 93 | 94 | airSensorSCD30.setMeasurementInterval(2); // CO2-Messung alle 5 s 95 | 96 | pinMode( 2 , INPUT); 97 | 98 | Serial.begin(115200); 99 | matrix.begin();// Matrix initialisieren 100 | delay(10); 101 | matrix.clear(); 102 | 103 | matrix.setTextColor(60); // Helligkeit begrenzen 104 | matrixausgabe.attach(0.08, matrixUpdate); // zyklisch aktualisieren 105 | 106 | //Serial.println(); 107 | WSpixels.begin();//-------------- Initialisierung Neopixel 108 | 109 | digitalWrite( 12 , HIGH ); //--- test LEDs 110 | 111 | digitalWrite( 13 , HIGH ); //--- test LEDs 112 | 113 | digitalWrite( 14 , HIGH ); //--- test LEDs 114 | 115 | delay(500); 116 | 117 | digitalWrite( 12 , LOW ); 118 | 119 | digitalWrite( 13 , LOW ); 120 | 121 | digitalWrite( 14 , LOW ); 122 | 123 | 124 | //Serial.println("128x64 OLED FeatherWing test"); 125 | display.begin(0x3C, true); // Address 0x3C default 126 | 127 | //Serial.println("OLED begun"); 128 | 129 | // Show image buffer on the display hardware. 130 | // Since the buffer is intialized with an Adafruit splashscreen 131 | // internally, this will display the splashscreen. 132 | display.display(); 133 | delay(1000); 134 | 135 | // Clear the buffer. 136 | display.clearDisplay(); 137 | display.display(); 138 | 139 | Wire.setClock(100000L); // 100 kHz SCD30 140 | Wire.setClockStretchLimit(200000L);// CO2-SCD30 141 | 142 | display.setRotation(1); 143 | display.setTextSize(1); 144 | display.setTextColor(SH110X_WHITE); 145 | display.setCursor(0,0); 146 | display.println("CO2 Ampel V21/1"); 147 | display.println("starten ..."); 148 | display.println(""); 149 | display.display(); 150 | 151 | delay(2000); 152 | 153 | if (!( digitalRead(2) )) 154 | { 155 | display.println("Kalibierung starten?"); 156 | display.println("Button(2) 5sec halten"); 157 | display.display(); 158 | 159 | delay( 5000 ); 160 | if (!( digitalRead(2) )) 161 | { 162 | 163 | digitalWrite( 12 , HIGH ); //--- test LEDs 164 | 165 | digitalWrite( 13 , HIGH ); //--- test LEDs 166 | 167 | digitalWrite( 14 , HIGH ); //--- test LEDs 168 | 169 | int i = 0; 170 | int sensor = 0; 171 | 172 | for (i= 1; i<= ( 24 ); i=i+1) 173 | { 174 | sensor = airSensorSCD30.getCO2() ; 175 | 176 | display.clearDisplay(); 177 | display.setRotation(0); 178 | display.display(); 179 | 180 | 181 | canvas.fillScreen(SH110X_BLACK); 182 | canvas.setRotation(1); 183 | 184 | canvas.setTextSize(1); 185 | canvas.setTextColor(SH110X_WHITE); 186 | canvas.setFont(&FreeMonoBold24pt7b); 187 | canvas.setCursor(0,45); 188 | if (CO2 <= 9999) { 189 | canvas.print(String(sensor)); 190 | } else 191 | { 192 | canvas.print("----"); 193 | } 194 | canvas.setFont(); 195 | canvas.setCursor(110,40); 196 | canvas.print("ppm"); 197 | canvas.setCursor(2,55); 198 | canvas.print("Kalibrierung "+String((i)*5)+"/120s"); 199 | 200 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128, SH110X_WHITE, SH110X_BLACK); 201 | display.display(); 202 | 203 | 204 | delay( 5000 ); 205 | yield(); 206 | } 207 | 208 | // Forced Calibration Sensirion SCD 30 209 | //Serial.print("Start SCD 30 calibration, please wait 20 s ..."); 210 | //delay(20000); 211 | airSensorSCD30.setAltitudeCompensation(0); // Altitude in m ü NN 212 | airSensorSCD30.setForcedRecalibrationFactor(400); // fresh air 213 | 214 | 215 | 216 | delay(1000); 217 | 218 | digitalWrite( 12 , LOW ); 219 | 220 | digitalWrite( 13 , LOW ); 221 | 222 | digitalWrite( 14 , LOW ); 223 | 224 | } 225 | } 226 | 227 | display.clearDisplay(); 228 | display.setRotation(0); 229 | display.display(); 230 | 231 | 232 | } 233 | 234 | void loop() { // Kontinuierliche Wiederholung 235 | CO2 = airSensorSCD30.getCO2(); 236 | 237 | canvas.fillScreen(SH110X_BLACK); 238 | canvas.setRotation(1); 239 | 240 | canvas.setTextSize(1); 241 | canvas.setTextColor(SH110X_WHITE); 242 | canvas.setFont(&FreeMonoBold24pt7b); 243 | canvas.setCursor(0,45); 244 | if (CO2 <= 9999) { 245 | canvas.print(String(CO2)); 246 | } else 247 | { 248 | canvas.print("----"); 249 | } 250 | canvas.setFont(); 251 | canvas.setCursor(110,40); 252 | canvas.print("ppm"); 253 | canvas.setCursor(2,55); 254 | //display.print("www.co2ampel.org - V1"); 255 | canvas.print(String(airSensorSCD30.getTemperature()-4)+"C"); 256 | canvas.setCursor(92,55); 257 | canvas.print(String(airSensorSCD30.getHumidity())+"%"); 258 | 259 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128, SH110X_WHITE, SH110X_BLACK); 260 | display.display(); 261 | 262 | // don't use this as long GPIO2 is set as INPUT to trigger sensor callibration! 263 | // heardbeat - on build in LED 264 | /* 265 | if (millis() > myTimeout + myTimer ) { 266 | myTimer = millis(); 267 | 268 | digitalWrite( 2 , LOW ); 269 | delay( 500 ); 270 | digitalWrite( 2 , HIGH ); 271 | } 272 | */ 273 | 274 | matrixAnzeige(String(String(( airSensorSCD30.getCO2() / 10 ))),1); 275 | Serial.println("CO2:"+String(String(CO2))); 276 | //Serial.println(); 277 | if (( ( CO2 ) < ( 1000 ) )) 278 | { 279 | digitalWrite( 12 , LOW ); 280 | digitalWrite( 13 , LOW ); 281 | digitalWrite( 14 , HIGH ); 282 | WSpixels.setPixelColor(0,(0<32)?0:32,(64<32)?64:32,(0<48)?0:48); 283 | WSpixels.show(); 284 | //Serial.print("green"); 285 | //Serial.println(); 286 | } 287 | else 288 | { 289 | if (( ( CO2 ) < ( 1400 ) )) 290 | { 291 | digitalWrite( 12 , LOW ); 292 | digitalWrite( 13 , HIGH ); 293 | digitalWrite( 14 , LOW ); 294 | WSpixels.setPixelColor(0,(64<32)?64:32,(64<32)?64:32,(0<48)?0:48); 295 | WSpixels.show(); 296 | //Serial.print("yellow"); 297 | //Serial.println(); 298 | } 299 | else 300 | { 301 | digitalWrite( 12 , HIGH ); 302 | digitalWrite( 13 , LOW ); 303 | digitalWrite( 14 , LOW ); 304 | WSpixels.setPixelColor(0,(64<32)?64:32,(0<32)?0:32,(0<48)?0:48); 305 | WSpixels.show(); 306 | //Serial.print("red"); 307 | //Serial.println(); 308 | } 309 | } 310 | delay( 2000 ); 311 | } 312 | -------------------------------------------------------------------------------- /CO2-Ampel-DIY-Octopus-SCD30-OLEDflckerfree.ino: -------------------------------------------------------------------------------- 1 | /* This program is distributed in the hope that it will be useful, 2 | but WITHOUT ANY WARRANTY; without even the implied warranty of 3 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4 | GNU General Public License for more details. */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // Adafruit_SH110X display = Adafruit_SH110X(64, 128, &Wire); // old syntax 17 | Adafruit_SH1107 display = Adafruit_SH1107(64, 128, &Wire); 18 | GFXcanvas1 canvas(64,128); 19 | 20 | unsigned long myTimer = 0; 21 | unsigned long myTimeout = 60000; // heart beat 60sec, LED build-in 22 | 23 | // OLED FeatherWing buttons map to different pins depending on board: 24 | #define BUTTON_A 0 25 | #define BUTTON_B 16 26 | #define BUTTON_C 2 27 | 28 | int CO2 = 0 ; 29 | //Reading CO2, humidity and temperature from the SCD30 By: Nathan Seidle SparkFun Electronics 30 | 31 | //https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library 32 | 33 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 34 | String matrixausgabe_text = " "; // Ausgabetext als globale Variable 35 | 36 | volatile int matrixausgabe_index = 0;// aktuelle Position in Matrix 37 | 38 | volatile int matrixausgabe_wait = 0;// warte bis Anzeige durchgelaufen ist 39 | 40 | //--------------------------------------- Charlieplex Matrix 41 | Adafruit_IS31FL3731_Wing matrix = Adafruit_IS31FL3731_Wing(); 42 | 43 | Ticker matrixausgabe; 44 | void matrixUpdate(){ // Update Charlieplexmatrix über Ticker 45 | int anzahlPixel=(matrixausgabe_text.length())*6; 46 | if (anzahlPixel > 15) { // Scrollen 47 | if (matrixausgabe_index >= -anzahlPixel) { 48 | matrix.clear(); 49 | matrix.setCursor(matrixausgabe_index,0); 50 | matrix.print(matrixausgabe_text); 51 | matrixausgabe_index--; 52 | } 53 | else { 54 | matrixausgabe_index = 12; 55 | matrixausgabe_wait=0; 56 | } 57 | } 58 | else {// nur 3 Zeichen lang-> kein Scroll 59 | matrix.setCursor(0,0); 60 | matrix.print(matrixausgabe_text); 61 | matrixausgabe_wait = 0; // no wait 62 | } 63 | } 64 | void matrixAnzeige(String text, int wait) { // Setze Ausgabetext 65 | if (matrixausgabe_text != text) { // update nur bei neuem Text 66 | matrix.clear(); 67 | matrixausgabe_index = 0; 68 | matrixausgabe_text = text; 69 | matrixausgabe_wait = wait; 70 | while (matrixausgabe_wait) delay(10); // warte bis Text einmal ausgegeben ist 71 | } 72 | }; 73 | 74 | Adafruit_NeoPixel WSpixels = Adafruit_NeoPixel((1<24)?1:24,15,NEO_RGB + NEO_KHZ800); 75 | 76 | void setup(){ // Einmalige Initialisierung 77 | WiFi.forceSleepBegin(); // Wifi off 78 | pinMode( 12 , OUTPUT); 79 | pinMode( 13 , OUTPUT); 80 | pinMode( 14 , OUTPUT); 81 | Wire.begin(); // ---- Initialisiere den I2C-Bus 82 | 83 | if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C"); 84 | 85 | if (airSensorSCD30.begin() == false) { 86 | Serial.println("The SCD30 did not respond. Please check wiring."); 87 | while(1) { 88 | yield(); 89 | delay(1); 90 | } 91 | } 92 | 93 | airSensorSCD30.setAutoSelfCalibration(false); // Sensirion no auto calibration 94 | 95 | airSensorSCD30.setMeasurementInterval(2); // CO2-Messung alle 5 s 96 | 97 | pinMode( 2 , INPUT); 98 | 99 | Serial.begin(115200); 100 | matrix.begin();// Matrix initialisieren 101 | delay(10); 102 | matrix.clear(); 103 | 104 | matrix.setTextColor(60); // Helligkeit begrenzen 105 | matrixausgabe.attach(0.08, matrixUpdate); // zyklisch aktualisieren 106 | 107 | //Serial.println(); 108 | WSpixels.begin();//-------------- Initialisierung Neopixel 109 | 110 | digitalWrite( 12 , HIGH ); //--- test LEDs 111 | 112 | digitalWrite( 13 , HIGH ); //--- test LEDs 113 | 114 | digitalWrite( 14 , HIGH ); //--- test LEDs 115 | 116 | delay(500); 117 | 118 | digitalWrite( 12 , LOW ); 119 | 120 | digitalWrite( 13 , LOW ); 121 | 122 | digitalWrite( 14 , LOW ); 123 | 124 | 125 | //Serial.println("128x64 OLED FeatherWing test"); 126 | display.begin(0x3C, true); // Address 0x3C default 127 | 128 | //Serial.println("OLED begun"); 129 | 130 | // Show image buffer on the display hardware. 131 | // Since the buffer is intialized with an Adafruit splashscreen 132 | // internally, this will display the splashscreen. 133 | display.display(); 134 | delay(1000); 135 | 136 | // Clear the buffer. 137 | display.clearDisplay(); 138 | display.display(); 139 | 140 | Wire.setClock(100000L); // 100 kHz SCD30 141 | Wire.setClockStretchLimit(200000L);// CO2-SCD30 142 | 143 | display.setRotation(1); 144 | display.setTextSize(1); 145 | display.setTextColor(SH110X_WHITE); 146 | display.setCursor(0,0); 147 | display.println("CO2 Ampel V21/1"); 148 | display.println("starten ..."); 149 | display.println(""); 150 | display.display(); 151 | 152 | delay(2000); 153 | 154 | if (!( digitalRead(2) )) 155 | { 156 | display.println("Kalibierung starten?"); 157 | display.println("Button(2) 5sec halten"); 158 | display.display(); 159 | 160 | delay( 5000 ); 161 | if (!( digitalRead(2) )) 162 | { 163 | 164 | digitalWrite( 12 , HIGH ); //--- test LEDs 165 | 166 | digitalWrite( 13 , HIGH ); //--- test LEDs 167 | 168 | digitalWrite( 14 , HIGH ); //--- test LEDs 169 | 170 | int i = 0; 171 | int sensor = 0; 172 | 173 | for (i= 1; i<= ( 24 ); i=i+1) 174 | { 175 | sensor = airSensorSCD30.getCO2() ; 176 | 177 | display.clearDisplay(); 178 | display.setRotation(0); 179 | display.display(); 180 | 181 | 182 | canvas.fillScreen(SH110X_BLACK); 183 | canvas.setRotation(1); 184 | 185 | canvas.setTextSize(1); 186 | canvas.setTextColor(SH110X_WHITE); 187 | canvas.setFont(&FreeMonoBold24pt7b); 188 | canvas.setCursor(0,45); 189 | if (CO2 <= 9999) { 190 | canvas.print(String(sensor)); 191 | } else 192 | { 193 | canvas.print("----"); 194 | } 195 | canvas.setFont(); 196 | canvas.setCursor(110,40); 197 | canvas.print("ppm"); 198 | canvas.setCursor(2,55); 199 | canvas.print("Kalibrierung "+String((i)*5)+"/120s"); 200 | 201 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128, SH110X_WHITE, SH110X_BLACK); 202 | display.display(); 203 | 204 | 205 | delay( 5000 ); 206 | yield(); 207 | } 208 | 209 | // Forced Calibration Sensirion SCD 30 210 | //Serial.print("Start SCD 30 calibration, please wait 20 s ..."); 211 | //delay(20000); 212 | airSensorSCD30.setAltitudeCompensation(0); // Altitude in m ü NN 213 | airSensorSCD30.setForcedRecalibrationFactor(400); // fresh air 214 | 215 | 216 | 217 | delay(1000); 218 | 219 | digitalWrite( 12 , LOW ); 220 | 221 | digitalWrite( 13 , LOW ); 222 | 223 | digitalWrite( 14 , LOW ); 224 | 225 | } 226 | } 227 | 228 | display.clearDisplay(); 229 | display.setRotation(0); 230 | display.display(); 231 | 232 | 233 | } 234 | 235 | void loop() { // Kontinuierliche Wiederholung 236 | CO2 = airSensorSCD30.getCO2(); 237 | 238 | canvas.fillScreen(SH110X_BLACK); 239 | canvas.setRotation(1); 240 | 241 | canvas.setTextSize(1); 242 | canvas.setTextColor(SH110X_WHITE); 243 | canvas.setFont(&FreeMonoBold24pt7b); 244 | canvas.setCursor(0,45); 245 | if (CO2 <= 9999) { 246 | canvas.print(String(CO2)); 247 | } else 248 | { 249 | canvas.print("----"); 250 | } 251 | canvas.setFont(); 252 | canvas.setCursor(110,40); 253 | canvas.print("ppm"); 254 | canvas.setCursor(2,55); 255 | //display.print("www.co2ampel.org - V1"); 256 | canvas.print(String(airSensorSCD30.getTemperature()-4)+"C"); 257 | canvas.setCursor(92,55); 258 | canvas.print(String(airSensorSCD30.getHumidity())+"%"); 259 | 260 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128, SH110X_WHITE, SH110X_BLACK); 261 | display.display(); 262 | 263 | // don't use this as long GPIO2 is set as INPUT to trigger sensor callibration! 264 | // heardbeat - on build in LED 265 | /* 266 | if (millis() > myTimeout + myTimer ) { 267 | myTimer = millis(); 268 | 269 | digitalWrite( 2 , LOW ); 270 | delay( 500 ); 271 | digitalWrite( 2 , HIGH ); 272 | } 273 | */ 274 | 275 | matrixAnzeige(String(String(( airSensorSCD30.getCO2() / 10 ))),1); 276 | Serial.println("CO2:"+String(String(CO2))); 277 | //Serial.println(); 278 | if (( ( CO2 ) < ( 1000 ) )) 279 | { 280 | digitalWrite( 12 , LOW ); 281 | digitalWrite( 13 , LOW ); 282 | digitalWrite( 14 , HIGH ); 283 | WSpixels.setPixelColor(0,(0<32)?0:32,(64<32)?64:32,(0<48)?0:48); 284 | WSpixels.show(); 285 | //Serial.print("green"); 286 | //Serial.println(); 287 | } 288 | else 289 | { 290 | if (( ( CO2 ) < ( 1400 ) )) 291 | { 292 | digitalWrite( 12 , LOW ); 293 | digitalWrite( 13 , HIGH ); 294 | digitalWrite( 14 , LOW ); 295 | WSpixels.setPixelColor(0,(64<32)?64:32,(64<32)?64:32,(0<48)?0:48); 296 | WSpixels.show(); 297 | //Serial.print("yellow"); 298 | //Serial.println(); 299 | } 300 | else 301 | { 302 | digitalWrite( 12 , HIGH ); 303 | digitalWrite( 13 , LOW ); 304 | digitalWrite( 14 , LOW ); 305 | WSpixels.setPixelColor(0,(64<32)?64:32,(0<32)?0:32,(0<48)?0:48); 306 | WSpixels.show(); 307 | //Serial.print("red"); 308 | //Serial.println(); 309 | } 310 | } 311 | delay( 2000 ); 312 | } 313 | -------------------------------------------------------------------------------- /CO2-Ampel-DIY-Octopus-SCD30-Neo-OLEDflickerfree.ino: -------------------------------------------------------------------------------- 1 | /* This program is distributed in the hope that it will be useful, 2 | but WITHOUT ANY WARRANTY; without even the implied warranty of 3 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4 | GNU General Public License for more details. */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | //Adafruit_SH110X display = Adafruit_SH110X(64, 128, &Wire); 18 | Adafruit_SH1107 display = Adafruit_SH1107(64, 128, &Wire); 19 | GFXcanvas1 canvas(64,128); 20 | 21 | unsigned long myTimer = 0; 22 | unsigned long myTimeout = 60000; // heart beat 60sec, LED build-in 23 | 24 | // OLED FeatherWing buttons map to different pins depending on board: 25 | #define BUTTON_A 0 26 | #define BUTTON_B 16 27 | #define BUTTON_C 2 28 | 29 | int CO2 = 0 ; 30 | //Reading CO2, humidity and temperature from the SCD30 By: Nathan Seidle SparkFun Electronics 31 | 32 | //https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library 33 | 34 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 35 | int IAQ = 0 ; 36 | int TEMP = 0 ; 37 | int HUMID = 0 ; 38 | /* 39 | Bosch BSEC Lib, https://github.com/BoschSensortec/BSEC-Arduino-library 40 | The BSEC software is only available for download or use after accepting the software license agreement. 41 | By using this library, you have agreed to the terms of the license agreement: 42 | https://ae-bst.resource.bosch.com/media/_tech/media/bsec/2017-07-17_ClickThrough_License_Terms_Environmentalib_SW_CLEAN.pdf */ 43 | Bsec iaqSensor; // Create an object of the class Bsec 44 | Ticker Bsec_Ticker; // schedule cyclic update via Ticker 45 | 46 | // ------------------------ Helper functions Bosch Bsec - Lib 47 | void checkIaqSensorStatus(void) 48 | { 49 | String output; 50 | if (iaqSensor.status != BSEC_OK) { 51 | if (iaqSensor.status < BSEC_OK) { 52 | output = "BSEC error code : " + String(iaqSensor.status); 53 | for (;;) { 54 | Serial.println(output); 55 | delay(500); 56 | } // Halt in case of failure 57 | } 58 | else { 59 | output = "BSEC warning code : " + String(iaqSensor.status); 60 | Serial.println(output); 61 | } 62 | } 63 | 64 | if (iaqSensor.bme680Status != BME680_OK) { 65 | if (iaqSensor.bme680Status < BME680_OK) { 66 | output = "BME680 error code : " + String(iaqSensor.bme680Status); 67 | for (;;){ 68 | Serial.println(output); 69 | delay(500); 70 | } // Halt in case of failure 71 | } 72 | else { 73 | output = "BME680 warning code : " + String(iaqSensor.bme680Status); 74 | Serial.println(output); 75 | } 76 | } 77 | } 78 | 79 | // Housekeeping: scheduled update using ticker-lib 80 | void iaqSensor_Housekeeping(){ // get new data 81 | iaqSensor.run(); 82 | } 83 | 84 | String matrixausgabe_text = " "; // Ausgabetext als globale Variable 85 | 86 | volatile int matrixausgabe_index = 0;// aktuelle Position in Matrix 87 | 88 | volatile int matrixausgabe_wait = 0;// warte bis Anzeige durchgelaufen ist 89 | 90 | //--------------------------------------- Charlieplex Matrix 91 | Adafruit_IS31FL3731_Wing matrix = Adafruit_IS31FL3731_Wing(); 92 | 93 | Ticker matrixausgabe; 94 | void matrixUpdate(){ // Update Charlieplexmatrix über Ticker 95 | int anzahlPixel=(matrixausgabe_text.length())*6; 96 | if (anzahlPixel > 15) { // Scrollen 97 | if (matrixausgabe_index >= -anzahlPixel) { 98 | matrix.clear(); 99 | matrix.setCursor(matrixausgabe_index,0); 100 | matrix.print(matrixausgabe_text); 101 | matrixausgabe_index--; 102 | } 103 | else { 104 | matrixausgabe_index = 12; 105 | matrixausgabe_wait=0; 106 | } 107 | } 108 | else {// nur 3 Zeichen lang-> kein Scroll 109 | matrix.setCursor(0,0); 110 | matrix.print(matrixausgabe_text); 111 | matrixausgabe_wait = 0; // no wait 112 | } 113 | } 114 | void matrixAnzeige(String text, int wait) { // Setze Ausgabetext 115 | if (matrixausgabe_text != text) { // update nur bei neuem Text 116 | matrix.clear(); 117 | matrixausgabe_index = 0; 118 | matrixausgabe_text = text; 119 | matrixausgabe_wait = wait; 120 | while (matrixausgabe_wait) delay(10); // warte bis Text einmal ausgegeben ist 121 | } 122 | }; 123 | 124 | //Adafruit_NeoPixel WSpixels = Adafruit_NeoPixel((1<24)?1:24,15,NEO_RGB + NEO_KHZ800); 125 | Adafruit_NeoPixel pixels = Adafruit_NeoPixel(2,13,NEO_GRBW + NEO_KHZ800); 126 | 127 | void setup(){ // Einmalige Initialisierung 128 | WiFi.forceSleepBegin(); // Wifi off 129 | 130 | Wire.begin(); // ---- Initialisiere den I2C-Bus 131 | 132 | if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C"); 133 | 134 | if (airSensorSCD30.begin() == false) { 135 | Serial.println("The SCD30 did not respond. Please check wiring."); 136 | while(1) { 137 | yield(); 138 | delay(1); 139 | } 140 | } 141 | 142 | airSensorSCD30.setAutoSelfCalibration(false); // Sensirion no auto calibration 143 | 144 | airSensorSCD30.setMeasurementInterval(2); // CO2-Messung alle 5 s 145 | 146 | 147 | iaqSensor.begin(BME680_I2C_ADDR_PRIMARY, Wire); 148 | String output = "\nBSEC library version " + String(iaqSensor.version.major) + "." + String(iaqSensor.version.minor) + "." + String(iaqSensor.version.major_bugfix) + "." + String(iaqSensor.version.minor_bugfix); 149 | Serial.println(output); 150 | checkIaqSensorStatus(); 151 | 152 | bsec_virtual_sensor_t sensorList[10] = { 153 | BSEC_OUTPUT_RAW_TEMPERATURE, 154 | BSEC_OUTPUT_RAW_PRESSURE, 155 | BSEC_OUTPUT_RAW_HUMIDITY, 156 | BSEC_OUTPUT_RAW_GAS, 157 | BSEC_OUTPUT_IAQ, 158 | BSEC_OUTPUT_STATIC_IAQ, 159 | BSEC_OUTPUT_CO2_EQUIVALENT, 160 | BSEC_OUTPUT_BREATH_VOC_EQUIVALENT, 161 | BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_TEMPERATURE, 162 | BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY, 163 | }; 164 | 165 | iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP); 166 | checkIaqSensorStatus(); 167 | iaqSensor_Housekeeping(); 168 | Bsec_Ticker.attach_ms(3000, iaqSensor_Housekeeping); 169 | 170 | pixels.begin();//-------------- Initialisierung Neopixel 171 | delay(1); 172 | pixels.show(); 173 | pixels.setPixelColor(0,0,0,0,0); // alle aus 174 | pixels.setPixelColor(1,0,0,0,0); 175 | pixels.show(); // und anzeigen 176 | 177 | pixels.setPixelColor(0,0,30,0,0); 178 | pixels.show(); 179 | 180 | 181 | pinMode( 2 , INPUT); 182 | 183 | Serial.begin(115200); 184 | matrix.begin();// Matrix initialisieren 185 | delay(10); 186 | matrix.clear(); 187 | 188 | matrix.setTextColor(60); // Helligkeit begrenzen 189 | matrixausgabe.attach(0.08, matrixUpdate); // zyklisch aktualisieren 190 | 191 | 192 | //Serial.println("128x64 OLED FeatherWing test"); 193 | display.begin(0x3C, true); // Address 0x3C default 194 | 195 | //Serial.println("OLED begun"); 196 | 197 | // Show image buffer on the display hardware. 198 | // Since the buffer is intialized with an Adafruit splashscreen 199 | // internally, this will display the splashscreen. 200 | display.display(); 201 | delay(1000); 202 | 203 | // Clear the buffer. 204 | display.clearDisplay(); 205 | display.display(); 206 | 207 | Wire.setClock(100000L); // 100 kHz SCD30 208 | Wire.setClockStretchLimit(200000L);// CO2-SCD30 209 | 210 | display.setRotation(1); 211 | display.setTextSize(1); 212 | display.setTextColor(SH110X_WHITE); 213 | display.setCursor(0,0); 214 | display.println("CO2 Ampel V21/1"); 215 | display.println("starten ..."); 216 | display.println(""); 217 | display.display(); 218 | 219 | delay(2000); 220 | 221 | if (!( digitalRead(2) )) 222 | { 223 | display.println("Kalibierung starten?"); 224 | display.println("Button(2) 5sec halten"); 225 | display.display(); 226 | 227 | delay( 5000 ); 228 | if (!( digitalRead(2) )) 229 | { 230 | 231 | 232 | int i = 0; 233 | int sensor = 0; 234 | 235 | for (i= 1; i<= ( 24 ); i=i+1) 236 | { 237 | sensor = airSensorSCD30.getCO2() ; 238 | 239 | display.clearDisplay(); 240 | display.setRotation(0); 241 | display.display(); 242 | 243 | 244 | canvas.fillScreen(SH110X_BLACK); 245 | canvas.setRotation(1); 246 | 247 | canvas.setTextSize(1); 248 | canvas.setTextColor(SH110X_WHITE); 249 | canvas.setFont(&FreeMonoBold24pt7b); 250 | canvas.setCursor(0,45); 251 | if (CO2 <= 9999) { 252 | canvas.print(String(sensor)); 253 | } else 254 | { 255 | canvas.print("----"); 256 | } 257 | canvas.setFont(); 258 | canvas.setCursor(110,40); 259 | canvas.print("ppm"); 260 | canvas.setCursor(2,55); 261 | canvas.print("Kalibrierung "+String((i)*5)+"/120s"); 262 | 263 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128, SH110X_WHITE, SH110X_BLACK); 264 | display.display(); 265 | 266 | 267 | delay( 5000 ); 268 | yield(); 269 | } 270 | 271 | // Forced Calibration Sensirion SCD 30 272 | //Serial.print("Start SCD 30 calibration, please wait 20 s ..."); 273 | //delay(20000); 274 | airSensorSCD30.setAltitudeCompensation(0); // Altitude in m ü NN 275 | airSensorSCD30.setForcedRecalibrationFactor(400); // fresh air 276 | 277 | 278 | 279 | delay(1000); 280 | 281 | } 282 | } 283 | 284 | display.clearDisplay(); 285 | display.setRotation(0); 286 | display.display(); 287 | 288 | 289 | 290 | } 291 | 292 | void loop() { // Kontinuierliche Wiederholung 293 | CO2 = airSensorSCD30.getCO2(); 294 | 295 | IAQ = iaqSensor.iaq ; 296 | TEMP = ( iaqSensor.temperature - 5 ) ; 297 | HUMID = iaqSensor.humidity ; 298 | 299 | canvas.fillScreen(SH110X_BLACK); 300 | canvas.setRotation(1); 301 | 302 | canvas.setTextSize(1); 303 | canvas.setTextColor(SH110X_WHITE); 304 | canvas.setFont(&FreeMonoBold24pt7b); 305 | canvas.setCursor(0,45); 306 | if (CO2 <= 9999) { 307 | canvas.print(String(CO2)); 308 | } else 309 | { 310 | canvas.print("----"); 311 | } 312 | canvas.setFont(); 313 | canvas.setCursor(110,40); 314 | canvas.print("ppm"); 315 | canvas.setCursor(2,55); 316 | //display.print("www.co2ampel.org - V1"); 317 | canvas.print(String(airSensorSCD30.getTemperature()-4)+"C"); 318 | canvas.setCursor(92,55); 319 | canvas.print(String(airSensorSCD30.getHumidity())+"%"); 320 | 321 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128, SH110X_WHITE, SH110X_BLACK); 322 | display.display(); 323 | 324 | // don't use this as long GPIO2 is set as INPUT to trigger sensor callibration! 325 | // heardbeat - on build in LED 326 | /* 327 | if (millis() > myTimeout + myTimer ) { 328 | myTimer = millis(); 329 | 330 | digitalWrite( 2 , LOW ); 331 | delay( 500 ); 332 | digitalWrite( 2 , HIGH ); 333 | } 334 | */ 335 | 336 | matrixAnzeige(String(String(( airSensorSCD30.getCO2() / 10 ))),1); 337 | Serial.println("CO2:"+String(String(CO2))); 338 | //Serial.println(); 339 | if (( ( CO2 ) < ( 1000 ) )) 340 | { 341 | //digitalWrite( 12 , LOW ); 342 | //digitalWrite( 13 , LOW ); 343 | //digitalWrite( 14 , HIGH ); 344 | //WSpixels.setPixelColor(0,(0<32)?0:32,(64<32)?64:32,(0<48)?0:48); 345 | //WSpixels.show(); 346 | //Serial.print("green"); 347 | //Serial.println(); 348 | } 349 | else 350 | { 351 | if (( ( CO2 ) < ( 1400 ) )) 352 | { 353 | //digitalWrite( 12 , LOW ); 354 | //digitalWrite( 13 , HIGH ); 355 | //digitalWrite( 14 , LOW ); 356 | //WSpixels.setPixelColor(0,(64<32)?64:32,(64<32)?64:32,(0<48)?0:48); 357 | //WSpixels.show(); 358 | //Serial.print("yellow"); 359 | //Serial.println(); 360 | } 361 | else 362 | { 363 | //digitalWrite( 12 , HIGH ); 364 | //digitalWrite( 13 , LOW ); 365 | //digitalWrite( 14 , LOW ); 366 | //WSpixels.setPixelColor(0,(64<32)?64:32,(0<32)?0:32,(0<48)?0:48); 367 | //WSpixels.show(); 368 | //Serial.print("red"); 369 | //Serial.println(); 370 | } 371 | } 372 | 373 | check_neo(); 374 | delay( 5000 ); 375 | } 376 | 377 | 378 | void check_neo() 379 | { 380 | if (( ( CO2 ) < ( 1000 ) )) 381 | { 382 | pixels.setPixelColor(0,0,30,0,0); 383 | pixels.show(); 384 | } 385 | else 386 | { 387 | if (( ( CO2 ) < ( 1400 ) )) 388 | { 389 | pixels.setPixelColor(0,30,30,0,0); 390 | pixels.show(); 391 | } 392 | else 393 | { 394 | pixels.setPixelColor(0,40,0,0,0); 395 | pixels.show(); 396 | } 397 | } 398 | if (( ( IAQ ) < ( 100 ) )) 399 | { 400 | pixels.setPixelColor(1,0,30,0,0); 401 | pixels.show(); 402 | } 403 | else 404 | { 405 | if (( ( IAQ ) < ( 250 ) )) 406 | { 407 | pixels.setPixelColor(1,30,30,0,0); 408 | pixels.show(); 409 | } 410 | else 411 | { 412 | pixels.setPixelColor(1,40,0,0,0); 413 | pixels.show(); 414 | } 415 | } 416 | } 417 | -------------------------------------------------------------------------------- /CO2-Ampel-DIY-Octopus-SCD30-WEB-AP-CAL.ino: -------------------------------------------------------------------------------- 1 | /* This program is distributed in the hope that it will be useful, 2 | but WITHOUT ANY WARRANTY; without even the implied warranty of 3 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4 | GNU General Public License for more details. */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | String matrixausgabe_text = " "; // Ausgabetext als globale Variable 11 | 12 | volatile int matrixausgabe_index = 0;// aktuelle Position in Matrix 13 | 14 | IPAddress myOwnIP; // ownIP for mDNS 15 | 16 | int CO2 = 0 ; 17 | //Reading CO2, humidity and temperature from the SCD30 By: Nathan Seidle SparkFun Electronics 18 | 19 | //https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library 20 | 21 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 22 | int doCal=0; // Kalibriermerker 23 | void CO2_Kalibrierfunktion(){ // Kalibrierfunktion 24 | // Forced Calibration Sensirion SCD 30 25 | Serial.print("Start SCD 30 calibration, please wait 20 s ...");delay(20000); 26 | airSensorSCD30.setAltitudeCompensation(0); // Altitude in m ü NN 27 | airSensorSCD30.setForcedRecalibrationFactor(400); // fresh air 28 | Serial.println(" done"); 29 | } 30 | 31 | 32 | ESP8266WebServer server(80); 33 | //------------------------------ Server Hompage html-Code 34 | const char INDEX_HTML_START[] = 35 | "" 36 | "" 37 | "" 38 | "" 39 | "" 40 | "IoT-Werkstatt Umwelt-Campus Birkenfeld" 41 | "" 42 | "" 43 | "
"; 44 | 45 | const char INDEX_HTML_SUBMIT[] = 46 | "
" 47 | "

" 48 | "Password: " 49 | "" 50 | "" 51 | "

" 52 | "
"; 53 | 54 | const char INDEX_HTML_END[] = 55 | "
" 56 | "" 57 | ""; 58 | 59 | String cal_passwort = "your password"; // Kalibrierpasswort 60 | String cal_message = ""; // Nachricht 61 | 62 | //------------------------------ Server Ausgabe Messwerte in Form einer html-Tabelle 63 | 64 | String messwertTabelle(){ 65 | String unit1=" C"; 66 | String unit2=" %"; 67 | String unit3=""; 68 | 69 | String nam1="Temperatur"; 70 | String nam2="Luftfeuchtigkeit"; 71 | String nam3=""; 72 | 73 | float mess1=airSensorSCD30.getTemperature(); 74 | float mess2=airSensorSCD30.getHumidity(); 75 | float mess3=0; 76 | //String html = "

Aktuelle Informationen:

"; 77 | String html = "

"; 78 | if (nam1.length()>0) 79 | html = html + ""; 80 | if (nam2.length()>0) 81 | html = html + ""; 82 | if (nam3.length()>0) 83 | html = html + ""; 84 | html=html+"
" + nam1 + ": " + String(mess1) + ""+ unit1 +"
" + nam2 + ": " + String(mess2) + ""+ unit2 +"
" + nam3 + ": " + String(mess3) + ""+ unit3 +"

"; 85 | return html; 86 | } 87 | //------------------------------ Server Ausgabe Bild in Abhängigkeit von Messert und Grenzen 88 | void serverSendFigure(){ 89 | String unit ="ppm"; 90 | String nam ="CO2"; 91 | float mess=CO2; 92 | float limit1 = 1000; 93 | float limit2 = 1400; 94 | if (mess < limit1) // grün 95 | server.sendContent(F("")); 96 | else 97 | if (mess>limit2) // rot 98 | server.sendContent(F("")); 99 | else // gelb 100 | server.sendContent(F("")); 101 | String val = String("

")+String(nam)+String(": ")+String(mess)+String(" ")+unit+ String("

"); 102 | server.sendContent(val); 103 | } 104 | //------------------------------ Server Unterprogramm zur Bearbeitung der Anfragen 105 | void serverHomepage() { 106 | if (server.hasArg("message")) {// Wenn Kalibrierpasswort eingetroffen, 107 | String input = server.arg("message"); // dann Text vom Client einlesen 108 | if (input == cal_passwort){ 109 | cal_message = "do calibration (20 s wait)"; 110 | doCal = 1; 111 | } else 112 | cal_message = "wrong password"; 113 | } else cal_message = ""; 114 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 115 | server.send ( 200, "text/html", INDEX_HTML_START); 116 | serverSendFigure(); // Ampel integrieren 117 | server.sendContent(F("")); 118 | server.sendContent(messwertTabelle()); 119 | if (cal_passwort != "your password") { 120 | server.sendContent(INDEX_HTML_SUBMIT); 121 | server.sendContent(cal_message); 122 | } 123 | server.sendContent(INDEX_HTML_END); 124 | } 125 | 126 | 127 | 128 | void setup(){ // Einmalige Initialisierung 129 | Serial.begin(115200); 130 | pinMode( 12 , OUTPUT); 131 | pinMode( 13 , OUTPUT); 132 | pinMode( 14 , OUTPUT); 133 | Wire.begin(); // ---- Initialisiere den I2C-Bus 134 | 135 | if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C"); 136 | 137 | if (airSensorSCD30.begin() == false) {Serial.println("The SCD30 did not respond. Please check wiring."); while(1) {yield(); delay(1);} } 138 | 139 | airSensorSCD30.setAutoSelfCalibration(false); // Sensirion no auto calibration 140 | 141 | airSensorSCD30.setMeasurementInterval(2); // CO2-Messung alle 5 s 142 | 143 | Serial.println(); 144 | //------------ HTML-Server initialisieren 145 | server.on("/", serverHomepage); 146 | server.begin();// Server starten 147 | //------------ eigenen WLAN - Accespoint aufbauen 148 | WiFi.softAP("MeinOctiWLAN","mind8Zeichen"); 149 | Serial.print("\nAccessPoint SSID:"); Serial.print("MeinOctiWLAN"); 150 | Serial.println (" IP:"+ WiFi.softAPIP().toString()); 151 | myOwnIP = WiFi.softAPIP(); 152 | matrixausgabe_text = String("Mein Netz:") + String("MeinOctiWLAN") + String( " IP:") + WiFi.softAPIP().toString(); 153 | matrixausgabe_index=0; 154 | 155 | digitalWrite( 12 , LOW ); 156 | 157 | digitalWrite( 13 , LOW ); 158 | 159 | digitalWrite( 14 , LOW ); 160 | 161 | Wire.setClock(100000L); // 100 kHz SCD30 162 | Wire.setClockStretchLimit(200000L);// CO2-SCD30 163 | } 164 | 165 | void loop() { // Kontinuierliche Wiederholung 166 | CO2 = airSensorSCD30.getCO2() ; 167 | Serial.print("CO2:"+String(String(CO2))); 168 | Serial.println(); 169 | webpage(); 170 | if (( ( CO2 ) < ( 1000 ) )) 171 | { 172 | digitalWrite( 12 , LOW ); 173 | digitalWrite( 13 , LOW ); 174 | digitalWrite( 14 , HIGH ); 175 | Serial.print("green"); 176 | Serial.println(); 177 | } 178 | else 179 | { 180 | if (( ( CO2 ) < ( 1400 ) )) 181 | { 182 | digitalWrite( 12 , LOW ); 183 | digitalWrite( 13 , HIGH ); 184 | digitalWrite( 14 , LOW ); 185 | Serial.print("yellow"); 186 | Serial.println(); 187 | } 188 | else 189 | { 190 | digitalWrite( 12 , HIGH ); 191 | digitalWrite( 13 , LOW ); 192 | digitalWrite( 14 , LOW ); 193 | Serial.print("red"); 194 | Serial.println(); 195 | } 196 | } 197 | delay( 2000 ); 198 | } 199 | 200 | void webpage() 201 | { 202 | //Block------------------------------ HTML-Server 203 | server.handleClient(); //Homepageanfragen versorgen 204 | delay(1); 205 | if (doCal) { 206 | CO2_Kalibrierfunktion(); // Kalibrierfunktion aufrufen 207 | doCal=0; 208 | }} 209 | -------------------------------------------------------------------------------- /QT_RP2040_CO2_NEO_BME_LCD_RING.ino: -------------------------------------------------------------------------------- 1 | /** check wiring and pin mapping first! **/ 2 | 3 | /* Wire - check pins_arduino.h for QT PY RP2040 /opt/arduino-1.8.13/portable/packages/rp2040/hardware/rp2040/1.4.0/variants/adafruitfeather 4 | #define PIN_WIRE0_SDA (24u) 5 | #define PIN_WIRE0_SCL (25u) 6 | #define PIN_WIRE1_SDA (22u) 7 | #define PIN_WIRE1_SCL (23u) 8 | */ 9 | 10 | /* Wire - check pins_arduino.h for Feather RP2040 /opt/arduino-1.8.13/portable/packages/rp2040/hardware/rp2040/1.2.1/variants/adafruitfeather 11 | #define PIN_WIRE0_SDA (2u) 12 | #define PIN_WIRE0_SCL (3u) 13 | */ 14 | 15 | /* Wire - check pins_arduino.h for M5ATOM Matrix /opt/arduino-1.8.13/portable/packages/esp32/hardware/esp32/1.0.6/variants/m5stack_atom 16 | // static const uint8_t SDA = 25; //26 17 | // static const uint8_t SCL = 21; //32 18 | */ 19 | 20 | // 21 | // Author Guido Burger, www.fab-lab.eu, sofware as is incl. 3rd parties, as referenced in the libs 22 | // www.co2ampel.org, #IoT Werkstatt 23 | // #CO2Ampel, #CO2Monitor 24 | // use with Adafruit QT PY RP2040, Feather RP2040, QT PY SAMD51 or M5Atom 25 | // 26 | 27 | // /opt/arduino-1.8.13/portable/packages/rp2040/hardware/rp2040/1.4.0/tools/uf2conv.py (want to change name NEW.UF2 ?) 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include "Adafruit_BME680.h" 36 | #include 37 | 38 | // only for OLED 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | LinearRegression lr; // define objects 46 | float values[2]; // define variables 47 | int zaehler = 0; // counting values for regression 48 | int forecast = 0; // in time[min] will be reach the next threshold 49 | double correlation; 50 | 51 | // as BSEC is not yet available for RP2040(?) we sue simple VOC/IAQ calc for now 52 | // VOC basic 53 | /** 54 | This IAQ and the ideas and concepts is Copyright (c) David Bird 2018. All rights to this IAQ 55 | and software are reserved. Any redistribution or reproduction of any part or all of the 56 | contents in any form is prohibited other than the following: 57 | 58 | You may print or download to a local hard disk extracts for your personal and non-commercial use only. 59 | You may copy the content to individual third parties for their personal use, but only if you 60 | acknowledge the author David Bird as the source of the material. 61 | You may not, except with my express written permission, distribute or commercially exploit the content. 62 | You may not transmit it or store it in any other website or other form of electronic retrieval 63 | system for commercial purposes. 64 | The above copyright ('as annotated') notice and this permission notice shall be included in 65 | all copies or substantial portions of the IAQ index and Software and where the software use 66 | is visible to an end-user. 67 | **/ 68 | 69 | // https://github.com/G6EJD/BME680-Example/blob/master/ESP32_bme680_CC_demo_03.ino 70 | float hum_weighting = 0.25; // so hum effect is 25% of the total air quality score 71 | float gas_weighting = 0.75; // so gas effect is 75% of the total air quality score 72 | 73 | int humidity_score, gas_score; 74 | float gas_reference = 2500; 75 | float hum_reference = 40; 76 | int getgasreference_count = 0; 77 | int gas_lower_limit = 10000; // Bad air quality limit 78 | int gas_upper_limit = 300000; // Good air quality limit 79 | 80 | /** config **/ 81 | 82 | String trafficLight = "green"; 83 | int greenLevel = 0; // threshold - enterng green level 84 | int yellowLevel = 800; // threshold - entering yellow level 85 | int redLevel = 1000; // threshold - entering red level 86 | int tempAdjust = -7; // compensation board heating RP2040 87 | 88 | double max_co2 = 0; 89 | double min_co2 = 0; 90 | int i = 0; 91 | 92 | SensirionI2CScd4x scd4x; 93 | Adafruit_BME680 bme; // I2C 94 | 95 | /**only ONE display option at a time **/ 96 | #define LCD //use Sparkfun SerLCD/RGB/3.3V/I2C 97 | //#define RING //use NeoPixel Ring with 20 Pixel 98 | //#define OLED //use Adafruit 128x64 OLED Wing 99 | //#define M5ATOM //use if HW is M5Atom Matrix 100 | 101 | #define PLOTTER //set to plot data with Arduino(TM) Plotter, otherwise debug output 102 | 103 | #define SEALEVELPRESSURE_HPA (1013.25) 104 | 105 | /** start **/ 106 | 107 | #ifdef M5ATOM 108 | // only for M5Atom 109 | #include // http://librarymanager/All#M5Atom https://github.com/m5stack/M5Atom 110 | #include // http://librarymanager/All#FastLED https://github.com/FastLED/FastLED 111 | #endif 112 | 113 | #ifdef LCD 114 | SerLCD lcd; // Initialize the library with default I2C address 0x72 115 | #endif 116 | 117 | #ifdef RING 118 | //Adafruit_NeoPixel WSpixels = Adafruit_NeoPixel((10<24)?10:24,23,NEO_GRB + NEO_KHZ800); //10 Bar at QT PY RP2040 QWIIC 119 | Adafruit_NeoPixel WSpixels = Adafruit_NeoPixel((20<24)?20:24,23,NEO_GRB + NEO_KHZ800); //20 Ring at QT PY RP2040 QWIIC 120 | 121 | //--------- Neopixel Messanzeige (Gauge) 122 | void WSGauge(float val, float limit1, float limit2, float delta, int seg, int dir){ 123 | int bright = 32; 124 | float current = 0; 125 | int i; 126 | val = round(val/delta)*delta; // Runden der Anzeige auf Delta-Schritte 127 | for (int k=0;k<=(seg-1);k++) { // alle Pixel 128 | current = (k+1)*delta; 129 | if (dir==1) i=k; 130 | else { 131 | i=seg-1-k; 132 | if (i == seg-1) i=0; 133 | else i=i+1; 134 | } // clockwise or opposite 135 | if ((val>=current) && (val < limit1)) // gruen 136 | WSpixels.setPixelColor(i,0,bright,0); 137 | else if ((val>=current) && (val <= limit2)) // gelb 138 | WSpixels.setPixelColor(i,bright/2,bright/2,0); 139 | else if ((val >= current) && (val > limit2)) // rot 140 | WSpixels.setPixelColor(i,bright,0,0); 141 | else 142 | WSpixels.setPixelColor(i,0,0,0); 143 | } 144 | WSpixels.show(); // Anzeige 145 | } 146 | #else 147 | Adafruit_NeoPixel pixels(1, 3, NEO_GRB + NEO_KHZ800); // QT PY RP2040 148 | //Adafruit_NeoPixel pixels = Adafruit_NeoPixel(1,10,NEO_GRBW + NEO_KHZ800); // QT PY SAMD21 149 | #endif 150 | 151 | #ifdef OLED 152 | Adafruit_SH110X display = Adafruit_SH110X(64, 128, &Wire1); // QT PY RP2040 use the QWIIC I2C second port 153 | //Adafruit_SH110X display = Adafruit_SH110X(64, 128, &Wire); // Feather RP2040 use the QWIIC I2C first port 154 | GFXcanvas1 canvas(64,128); // memory buffer for flicker-free display 155 | #endif 156 | 157 | void printUint16Hex(uint16_t value) { 158 | Serial.print(value < 4096 ? "0" : ""); 159 | Serial.print(value < 256 ? "0" : ""); 160 | Serial.print(value < 16 ? "0" : ""); 161 | Serial.print(value, HEX); 162 | } 163 | 164 | void printSerialNumber(uint16_t serial0, uint16_t serial1, uint16_t serial2) { 165 | Serial.print("Serial: 0x"); 166 | printUint16Hex(serial0); 167 | printUint16Hex(serial1); 168 | printUint16Hex(serial2); 169 | Serial.println(); 170 | } 171 | 172 | void setup() { 173 | 174 | #ifdef M5ATOM 175 | M5.begin(true, true, true); // (Serial, I2C, NeoPixel) 176 | /* 177 | pinMode(22, INPUT); // PIN (INPUT, OUTPUT, ) 178 | pinMode(19, INPUT); // PIN (INPUT, OUTPUT, ) 179 | pinMode(23, INPUT); // PIN (INPUT, OUTPUT, ) 180 | pinMode(33, INPUT); // PIN (INPUT, OUTPUT, ANALOG) 181 | pinMode(26, INPUT); // GROVE(INPUT, OUTPUT, ANALOG) 182 | pinMode(32, INPUT); // GROVE(INPUT, OUTPUT, ANALOG) 183 | pinMode(12, OUTPUT_OPEN_DRAIN); 184 | digitalWrite(12, HIGH); 185 | */ 186 | #endif 187 | 188 | Serial.begin(115200); 189 | Wire.begin(); 190 | 191 | #ifdef LCD 192 | Wire1.begin(); 193 | lcd.begin(Wire1); //Set up the LCD for I2C communicatiom 194 | //lcd.setBacklight(255, 255, 255); //Set backlight to bright white 195 | lcd.setContrast(5); //Set contrast. Lower to 0 for higher contrast. 196 | lcd.disableSystemMessages(); 197 | lcd.clear(); //Clear the display - this moves the cursor to home position as well 198 | lcd.setCursor (0,0); 199 | lcd.print("www.co2ampel.org"); 200 | lcd.setCursor (0,1); 201 | lcd.print("Version 1.2"); 202 | // check RGB lights 203 | lcd.setBacklight(0, 0, 0); //black is off 204 | delay(500); 205 | lcd.setBacklight(255, 0, 0); //red 206 | delay(1000); 207 | lcd.setBacklight(255, 255, 0); //yellow 208 | delay(1000); 209 | lcd.setBacklight(0, 255, 0); //green 210 | delay(500); 211 | #endif 212 | 213 | #ifdef RING 214 | WSpixels.begin();//-------------- Initialisierung Neopixel 215 | WSpixels.show(); 216 | #else 217 | pixels.begin(); // INITIALIZE NeoPixel strip object (REQUIRED) 218 | // check RGB lights 219 | pixels.setPixelColor(0,0,30,0,0); 220 | pixels.show(); 221 | delay(1000); 222 | pixels.setPixelColor(0,30,30,0,0); 223 | pixels.show(); 224 | delay(1000); 225 | pixels.setPixelColor(0,40,0,0,0); 226 | pixels.show(); 227 | delay(1000); 228 | pixels.setPixelColor(0,0,0,0,0); // alle aus 229 | pixels.show(); 230 | #endif 231 | 232 | uint16_t error; 233 | char errorMessage[256]; 234 | 235 | scd4x.begin(Wire); 236 | 237 | // stop potentially previously started measurement 238 | error = scd4x.stopPeriodicMeasurement(); 239 | if (error) { 240 | Serial.print("Error trying to execute stopPeriodicMeasurement(): "); 241 | errorToString(error, errorMessage, 256); 242 | Serial.println(errorMessage); 243 | } 244 | 245 | uint16_t serial0; 246 | uint16_t serial1; 247 | uint16_t serial2; 248 | error = scd4x.getSerialNumber(serial0, serial1, serial2); 249 | if (error) { 250 | Serial.print("Error trying to execute getSerialNumber(): "); 251 | errorToString(error, errorMessage, 256); 252 | Serial.println(errorMessage); 253 | } else { 254 | //printSerialNumber(serial0, serial1, serial2); 255 | } 256 | 257 | // Start Measurement 258 | error = scd4x.startPeriodicMeasurement(); 259 | if (error) { 260 | Serial.print("Error trying to execute startPeriodicMeasurement(): "); 261 | errorToString(error, errorMessage, 256); 262 | Serial.println(errorMessage); 263 | } 264 | 265 | if (!bme.begin(0x76)) { 266 | Serial.println("Could not find a valid BME680 sensor, check wiring!"); 267 | delay(100); 268 | } 269 | 270 | // Set up oversampling and filter initialization 271 | bme.setTemperatureOversampling(BME680_OS_8X); 272 | bme.setHumidityOversampling(BME680_OS_2X); 273 | bme.setPressureOversampling(BME680_OS_4X); 274 | bme.setIIRFilterSize(BME680_FILTER_SIZE_3); 275 | bme.setGasHeater(320, 150); // 320*C for 150 ms 276 | 277 | // VOC basic 278 | // Now run the sensor to normalise the readings, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. 279 | // The sensor takes ~30-mins to fully stabilise 280 | GetGasReference(); 281 | 282 | #ifdef OLED 283 | //Serial.println("128x64 OLED FeatherWing test"); 284 | display.begin(0x3C, true); // Address 0x3C default 285 | display.clearDisplay(); // Clear the buffer. 286 | display.setRotation(0); 287 | display.display(); 288 | #endif 289 | 290 | //Serial.println("Waiting for first measurement... (5 sec)"); 291 | delay (5000); 292 | } 293 | 294 | void loop() { 295 | 296 | #ifdef M5ATOM 297 | M5.update(); 298 | #endif 299 | 300 | uint16_t error; 301 | char errorMessage[256]; 302 | 303 | // Read Measurement 304 | uint16_t co2; 305 | float temperature; 306 | float humidity; 307 | 308 | // VOC basic 309 | /* 310 | Serial.println("Sensor Readings:"); 311 | Serial.println(" Temperature = " + String(bme.readTemperature(), 2) + "°C"); 312 | Serial.println(" Pressure = " + String(bme.readPressure() / 100.0F) + " hPa"); 313 | Serial.println(" Humidity = " + String(bme.readHumidity(), 1) + "%"); 314 | Serial.println(" Gas = " + String(gas_reference) + " ohms\n"); 315 | */ 316 | humidity_score = GetHumidityScore(); 317 | gas_score = GetGasScore(); 318 | 319 | //Combine results for the final IAQ index value (0-100% where 100% is good quality air) 320 | float air_quality_score = humidity_score + gas_score; 321 | if ((getgasreference_count++) % 5 == 0) GetGasReference(); 322 | //Serial.println(CalculateIAQ(air_quality_score)); 323 | //delay(2000); 324 | 325 | // read data from SCD4x 326 | error = scd4x.readMeasurement(co2, temperature, humidity); 327 | if (error) { 328 | Serial.print("Error trying to execute readMeasurement(): "); 329 | errorToString(error, errorMessage, 256); 330 | Serial.println(errorMessage); 331 | } 332 | 333 | temperature = temperature + tempAdjust; // adjust to hw ... eg -7C 334 | 335 | 336 | 337 | i++; 338 | 339 | if (max_co2 < co2) { 340 | max_co2 = co2; 341 | } 342 | 343 | if (min_co2 > co2) { 344 | min_co2 = co2; 345 | } else { 346 | if (min_co2 == 0) { 347 | min_co2 = co2; 348 | } 349 | } 350 | 351 | Serial.println (min_co2); 352 | Serial.println (max_co2); 353 | 354 | // 5 sec sample -> 45 min = 540, max / min pro Schulstunde 355 | if (i > 540) { 356 | max_co2 = 0; 357 | min_co2 = 0; 358 | i = 0; 359 | } 360 | 361 | 362 | 363 | // read data from BME68x 364 | if (! bme.performReading()) { 365 | Serial.println("Failed to perform reading :("); 366 | return; 367 | } 368 | 369 | // calc linear regression for x data points 370 | zaehler ++; 371 | lr.Data(co2); 372 | 373 | if (zaehler > 9) { 374 | // calculation Linear Regression of last 10 data points 375 | /* 376 | Serial.print(lr.Samples()); Serial.println(" Point Linear Regression Calculation..."); 377 | Serial.print("Correlation: "); Serial.println(lr.Correlation()); 378 | Serial.print("Values: "); lr.Parameters(values); Serial.print("Y = "); Serial.print(values[0],4); Serial.print("*X + "); Serial.println(values[1],4); 379 | Serial.print("Values: "); lr.Parameters(values); Serial.print(" a = "); Serial.print(values[0],4); Serial.print(" b = "); Serial.println(values[1],4); 380 | Serial.print("Degree(°): "); Serial.print(57.2957795*atan(values[0]),2); Serial.println(""); // convert rad to deg 381 | Serial.print("Time(s): "); Serial.print((1000-values[1])/values[0]*5,2); Serial.println(""); 382 | */ 383 | lr.Samples(); 384 | correlation = lr.Correlation(); 385 | lr.Parameters(values); 386 | 387 | if (trafficLight == "green") { 388 | forecast = int((yellowLevel-values[1])/values[0]*5/60); // forecast till 800 ppm, steps 5s, in minute 389 | //Serial.println(yellowLevel); // 390 | } 391 | 392 | if (trafficLight == "yellow") { 393 | forecast = int((redLevel-values[1])/values[0]*5/60); // forecast till 1000 ppm, steps 5s, in minute 394 | //Serial.println(redLevel); // 395 | } 396 | 397 | #ifndef PLOTTER 398 | Serial.print(correlation); // 399 | Serial.print(": y = "); Serial.print(values[0],2); Serial.print("*x + "); Serial.println(values[1],2); // 400 | Serial.println(trafficLight); // 401 | Serial.print("Time(min): "); Serial.print(forecast); Serial.println(""); // 402 | #endif 403 | 404 | // Reset 405 | lr.Reset(); 406 | zaehler = 0; 407 | } 408 | 409 | #ifdef PLOTTER 410 | Serial.print("CO2:"); 411 | Serial.print(co2); 412 | Serial.print(" Temperature:"); 413 | Serial.print(temperature); 414 | Serial.print(" Humidity:"); 415 | Serial.print(humidity); 416 | Serial.print(" IAQ:"); 417 | Serial.println( (100 - air_quality_score)*5); 418 | #else 419 | // print SCD4x 420 | Serial.print("SCD4x - Co2:"); 421 | Serial.print(co2); 422 | Serial.print(" ppm, "); 423 | 424 | Serial.print("Temperature:"); 425 | Serial.print(temperature); 426 | Serial.print(" *C, "); 427 | 428 | Serial.print("Humidity:"); 429 | Serial.print(humidity); 430 | Serial.println(" %"); 431 | 432 | // print BME68x 433 | Serial.print("BME68x - Temperature: "); 434 | Serial.print(bme.temperature); 435 | Serial.print(" *C, "); 436 | 437 | Serial.print("Pressure: "); 438 | Serial.print(bme.pressure / 100.0); 439 | Serial.print(" hPa,"); 440 | 441 | Serial.print("Humidity: "); 442 | Serial.print(bme.humidity); 443 | Serial.print(" %, "); 444 | 445 | Serial.print("Gas: "); 446 | Serial.print(bme.gas_resistance / 1000.0); 447 | Serial.print(" KOhms, "); 448 | 449 | Serial.print("Altitude: "); 450 | Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA)); 451 | Serial.println(" m"); 452 | 453 | // Temperature RP2040 core 454 | Serial.printf("Core temperature: %2.1fC\n", analogReadTemp()); 455 | Serial.println(); 456 | #endif 457 | 458 | #ifdef LCD 459 | lcd.clear(); 460 | lcd.setCursor (0,0); 461 | lcd.print("CO2:"); 462 | lcd.setCursor(5,0); 463 | lcd.print(co2); 464 | lcd.setCursor (9,0); 465 | lcd.print("ppm"); 466 | #endif 467 | 468 | #ifdef RING 469 | // WSGauge(co2,800,1000,100,10,true); //10 Bar 470 | WSGauge(co2,800,1000,100,20,true); //20 Ring 471 | #else 472 | 473 | // Ampel - Traffic Light: <800 green, >800 yellow, >1000 red, adopt to your requirements! 474 | if (( ( co2 ) < ( yellowLevel ) )) 475 | { 476 | pixels.setPixelColor(0,0,30,0,0); 477 | pixels.show(); 478 | 479 | #ifdef LCD 480 | lcd.setBacklight(0, 255, 0); //green 481 | #endif 482 | trafficLight = "green"; 483 | 484 | #ifdef M5ATOM 485 | for (int i = 0; i < 25; i++) { 486 | M5.dis.drawpix(i, 0xff0000); //green 487 | } 488 | #endif 489 | } 490 | else 491 | { 492 | if (( ( co2 ) < ( redLevel ) )) 493 | { 494 | pixels.setPixelColor(0,30,30,0,0); 495 | pixels.show(); 496 | 497 | #ifdef LCD 498 | lcd.setBacklight(255, 255, 0); //yellow 499 | #endif 500 | trafficLight = "yellow"; 501 | 502 | #ifdef M5ATOM 503 | for (int i = 0; i < 25; i++) { 504 | M5.dis.drawpix(i, 0xffff00); //yellow 505 | } 506 | #endif 507 | } 508 | else 509 | { 510 | pixels.setPixelColor(0,40,0,0,0); 511 | pixels.show(); 512 | 513 | #ifdef LCD 514 | lcd.setBacklight(255, 0, 0); //red 515 | #endif 516 | trafficLight = "red"; 517 | 518 | #ifdef M5ATOM 519 | for (int i = 0; i < 25; i++) { 520 | M5.dis.drawpix(i, 0x00ff00); //red 521 | } 522 | #endif 523 | } 524 | } // VOC 525 | #endif 526 | 527 | #ifdef OLED 528 | canvas.fillScreen(SH110X_BLACK); 529 | canvas.setRotation(1); 530 | canvas.setFont(); 531 | canvas.setCursor(2,5); 532 | canvas.print("#CO2Ampel.org - V1.2"); 533 | 534 | canvas.setTextSize(1); 535 | canvas.setTextColor(SH110X_WHITE); 536 | canvas.setFont(&FreeMonoBold24pt7b); 537 | canvas.setCursor(0,47); 538 | 539 | // for SCD40 limit, adjust for SCD41 to 5000ppm 540 | if (co2 <= 2000) { 541 | canvas.print(String(co2)); 542 | } else 543 | { 544 | canvas.print("----"); 545 | } 546 | canvas.setFont(); 547 | canvas.setCursor(110,42); 548 | canvas.print("ppm"); 549 | canvas.setCursor(2,55); 550 | canvas.print(String(temperature)+"C"); 551 | 552 | // VOC basic 553 | canvas.setCursor(55,55); 554 | //canvas.print(String(int(bme.gas_resistance / 1000.0))+"R"); 555 | canvas.print(String(int(100 - air_quality_score)*5)+"Q"); //IAQ Index 556 | //Serial.println(CalculateIAQ(air_quality_score)); //IAQ classification 557 | 558 | canvas.setCursor(92,55); 559 | canvas.print(String(humidity)+"%"); //SCD40 560 | //canvas.print(String(bme.humidity)+"%"); //BME68x 561 | 562 | // forecast 563 | canvas.setCursor(115,20); 564 | canvas.print(" "); // clear to avoid "-" leading a single digit value 565 | canvas.setCursor(115,20); 566 | if (correlation > 0.4 && forecast < 99) { // we got stable forecast and < 99 min 567 | canvas.print(String(forecast)); 568 | } else { 569 | canvas.print("--"); 570 | } 571 | 572 | display.drawBitmap (0,0, canvas.getBuffer(), 64, 128,SH110X_WHITE,SH110X_BLACK); 573 | display.display(); 574 | #endif 575 | 576 | delay(5000); 577 | } 578 | 579 | /* 580 | // Running on core1 581 | void setup1() { 582 | } 583 | 584 | void loop1() { 585 | } 586 | */ 587 | 588 | 589 | 590 | 591 | 592 | // VOC basic 593 | void GetGasReference() { 594 | // Now run the sensor for a burn-in period, then use combination of relative humidity and gas resistance to estimate indoor air quality as a percentage. 595 | //Serial.println("Getting a new gas reference value"); 596 | int readings = 10; 597 | for (int i = 1; i <= readings; i++) { // read gas for 10 x 0.150mS = 1.5secs 598 | gas_reference += bme.readGas(); 599 | } 600 | gas_reference = gas_reference / readings; 601 | //Serial.println("Gas Reference = "+String(gas_reference,3)); 602 | } 603 | 604 | String CalculateIAQ(int score) { 605 | String IAQ_text = "air quality is "; 606 | score = (100 - score) * 5; 607 | if (score >= 301) IAQ_text += "Hazardous"; 608 | else if (score >= 201 && score <= 300 ) IAQ_text += "Very Unhealthy"; 609 | else if (score >= 176 && score <= 200 ) IAQ_text += "Unhealthy"; 610 | else if (score >= 151 && score <= 175 ) IAQ_text += "Unhealthy for Sensitive Groups"; 611 | else if (score >= 51 && score <= 150 ) IAQ_text += "Moderate"; 612 | else if (score >= 00 && score <= 50 ) IAQ_text += "Good"; 613 | Serial.print("IAQ Score = " + String(score) + ", "); 614 | return IAQ_text; 615 | } 616 | 617 | int GetHumidityScore() { //Calculate humidity contribution to IAQ index 618 | float current_humidity = bme.readHumidity(); 619 | if (current_humidity >= 38 && current_humidity <= 42) // Humidity +/-5% around optimum 620 | humidity_score = 0.25 * 100; 621 | else 622 | { // Humidity is sub-optimal 623 | if (current_humidity < 38) 624 | humidity_score = 0.25 / hum_reference * current_humidity * 100; 625 | else 626 | { 627 | humidity_score = ((-0.25 / (100 - hum_reference) * current_humidity) + 0.416666) * 100; 628 | } 629 | } 630 | return humidity_score; 631 | } 632 | 633 | int GetGasScore() { 634 | //Calculate gas contribution to IAQ index 635 | gas_score = (0.75 / (gas_upper_limit - gas_lower_limit) * gas_reference - (gas_lower_limit * (0.75 / (gas_upper_limit - gas_lower_limit)))) * 100.00; 636 | if (gas_score > 75) gas_score = 75; // Sometimes gas readings can go outside of expected scale maximum 637 | if (gas_score < 0) gas_score = 0; // Sometimes gas readings can go outside of expected scale minimum 638 | return gas_score; 639 | } 640 | -------------------------------------------------------------------------------- /co2_epaper_webserver.ino: -------------------------------------------------------------------------------- 1 | /* This program is distributed in the hope that it will be useful, 2 | but WITHOUT ANY WARRANTY; without even the implied warranty of 3 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 4 | GNU General Public License for more details. */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "Adafruit_EPD.h" // requires Adafruit BusIO 11 | 12 | String matrixausgabe_text = " "; // Ausgabetext als globale Variable 13 | 14 | volatile int matrixausgabe_index = 0;// aktuelle Position in Matrix 15 | 16 | IPAddress myOwnIP; // ownIP for mDNS 17 | 18 | float mess; 19 | 20 | //Reading CO2, humidity and temperature from the SCD30 By: Nathan Seidle SparkFun Electronics 21 | 22 | //https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library 23 | 24 | SCD30 airSensorSCD30; // Objekt SDC30 Umweltsensor 25 | void CO2_Kalibrierfunktion(){ // Kalibrierfunktion 26 | // Forced Calibration Sensirion SCD 30 27 | Serial.print("Start SCD 30 calibration, please wait 30 s ...");delay(30000); 28 | airSensorSCD30.setAltitudeCompensation(0); // Altitude in m ü NN 29 | airSensorSCD30.setForcedRecalibrationFactor(400); // fresh air 30 | Serial.println(" done"); 31 | } 32 | 33 | 34 | ESP8266WebServer server(80); 35 | //------------------------------ Server Hompage html-Code 36 | const char INDEX_HTML_START[] = 37 | "" 38 | "" 39 | "" 40 | "" 41 | "" 42 | "IoT-Werkstatt Umwelt-Campus Birkenfeld" 43 | "" 44 | "" 45 | "
"; 46 | 47 | const char INDEX_HTML_SUBMIT[] = 48 | "
" 49 | "

" 50 | "Passwort: " 51 | "" 52 | "" 53 | "

" 54 | "
"; 55 | 56 | const char INDEX_HTML_END[] = 57 | "
" 58 | "" 59 | ""; 60 | 61 | String cal_passwort = "calscd30"; // Kalibrierpasswort 62 | String cal_message = ""; // Nachricht 63 | 64 | //------------------------------ Server Ausgabe Messwerte in Form einer html-Tabelle 65 | 66 | String messwertTabelle(){ 67 | String unit1="%"; 68 | String unit2="C"; 69 | String unit3=""; 70 | 71 | String nam1="Luftfeuchtigkeit"; 72 | String nam2="Temperatur"; 73 | String nam3=""; 74 | 75 | float mess1=airSensorSCD30.getHumidity(); 76 | float mess2=airSensorSCD30.getTemperature()-3; 77 | float mess3=0; 78 | //String html = "

Aktuelle Informationen:

"; 79 | String html = "

"; 80 | if (nam1.length()>0) 81 | html = html + ""; 82 | if (nam2.length()>0) 83 | html = html + ""; 84 | if (nam3.length()>0) 85 | html = html + ""; 86 | html=html+"
" + nam1 + ": " + String(mess1) + ""+ unit1 +"
" + nam2 + ": " + String(mess2) + ""+ unit2 +"
" + nam3 + ": " + String(mess3) + ""+ unit3 +"

"; 87 | return html; 88 | } 89 | //------------------------------ Server Ausgabe Bild in Abhängigkeit von Messert und Grenzen 90 | void serverSendFigure(){ 91 | String unit ="ppm"; 92 | String nam ="CO2"; 93 | 94 | float limit1 = 1000; 95 | float limit2 = 1400; 96 | if (mess < limit1) // grün 97 | server.sendContent(F("")); 98 | else 99 | if (mess>limit2) // rot 100 | server.sendContent(F("")); 101 | else // gelb 102 | server.sendContent(F("")); 103 | String val = String("

")+String(nam)+String(": ")+String(mess)+String(" ")+unit+ String("

"); 104 | server.sendContent(val); 105 | } 106 | //------------------------------ Server Unterprogramm zur Bearbeitung der Anfragen 107 | void serverHomepage() { 108 | if (server.hasArg("message")) {// Wenn Kalibrierpasswort eingetroffen, 109 | String input = server.arg("message"); // dann Text vom Client einlesen 110 | if (input == cal_passwort){ 111 | CO2_Kalibrierfunktion(); // Kalibrierfunktion aufrufen 112 | cal_message = "calibration done"; 113 | } else 114 | cal_message = "wrong password"; 115 | } else cal_message = ""; 116 | server.setContentLength(CONTENT_LENGTH_UNKNOWN); 117 | server.send ( 200, "text/html", INDEX_HTML_START); 118 | serverSendFigure(); // Ampel integrieren 119 | server.sendContent(F("")); 120 | server.sendContent(messwertTabelle()); 121 | if (cal_passwort != "calscd30") { 122 | server.sendContent(INDEX_HTML_SUBMIT); 123 | server.sendContent(cal_message); 124 | } 125 | server.sendContent(INDEX_HTML_END); 126 | } 127 | 128 | #define EPD_CS 0//10 129 | #define EPD_DC 15//9 130 | #define SRAM_CS 16//11 131 | #define EPD_RESET -1 // can set to -1 and share with microcontroller Reset! 132 | #define EPD_BUSY -1 // can set to -1 to not use a pin (will wait a fixed delay) 133 | 134 | Adafruit_IL0373 display(212, 104, EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY); 135 | 136 | #define COLOR1 EPD_BLACK 137 | #define COLOR2 EPD_RED 138 | 139 | void setup(){ // Einmalige Initialisierung 140 | Serial.begin(115200); 141 | 142 | Wire.begin(); // ---- Initialisiere den I2C-Bus 143 | 144 | if (Wire.status() != I2C_OK) Serial.println("Something wrong with I2C"); 145 | 146 | if (airSensorSCD30.begin() == false) {Serial.println("The SCD30 did not respond. Please check wiring."); while(1) {yield(); delay(1);} } 147 | 148 | airSensorSCD30.setAutoSelfCalibration(false); // Sensirion no auto calibration 149 | 150 | airSensorSCD30.setMeasurementInterval(2); // CO2-Messung alle 5 s 151 | 152 | //------------ HTML-Server initialisieren 153 | server.on("/", serverHomepage); 154 | server.begin();// Server starten 155 | //------------ eigenen WLAN - Accespoint aufbauen 156 | WiFi.softAP("co2ampel","co2ampel"); 157 | Serial.print("\nAccessPoint SSID:"); Serial.print("MeinOctiWLAN"); 158 | Serial.println (" IP:"+ WiFi.softAPIP().toString()); 159 | myOwnIP = WiFi.softAPIP(); 160 | matrixausgabe_text = String("Mein Netz:") + String("MeinOctiWLAN") + String( " IP:") + WiFi.softAPIP().toString(); 161 | matrixausgabe_index=0; 162 | 163 | Wire.setClock(100000L); // 100 kHz SCD30 164 | Wire.setClockStretchLimit(200000L);// CO2-SCD30 165 | 166 | display.begin(); 167 | display.clearBuffer(); 168 | display.display(); 169 | } 170 | 171 | void loop() { // Kontinuierliche Wiederholung 172 | //Block------------------------------ HTML-Server 173 | 174 | mess=airSensorSCD30.getCO2(); 175 | 176 | server.handleClient(); //Homepageanfragen versorgen 177 | delay(1); 178 | 179 | display.clearBuffer(); 180 | display.setCursor(45, 10); 181 | display.setTextSize(2); 182 | display.setTextColor(EPD_BLACK); 183 | display.print("CO2-Ampel"); 184 | 185 | display.setCursor(55, 30); 186 | display.setTextSize(1); 187 | display.setTextColor(EPD_BLACK); 188 | display.print("#IoT-Werkstatt"); 189 | 190 | display.setTextSize(4); 191 | display.setCursor(60, 60); 192 | display.setTextColor(EPD_RED); 193 | display.print(String(abs(mess))); 194 | 195 | display.setTextSize(2); 196 | display.setCursor(10, 60); 197 | display.setTextColor(EPD_BLACK); 198 | display.print(String(abs(airSensorSCD30.getTemperature()-3))+"C"); 199 | 200 | display.setTextSize(2); 201 | display.setCursor(170, 60); 202 | display.setTextColor(EPD_BLACK); 203 | display.print(String(abs(airSensorSCD30.getHumidity()))+"%"); 204 | display.setCursor(170, 80); 205 | display.print("ppm"); 206 | 207 | display.display(); 208 | 209 | 210 | 211 | server.handleClient(); //Homepageanfragen versorgen 212 | delay(1); 213 | delay( 5000 ); 214 | server.handleClient(); //Homepageanfragen versorgen 215 | delay(1); 216 | delay( 5000 ); 217 | server.handleClient(); //Homepageanfragen versorgen 218 | delay(1); 219 | delay( 5000 ); 220 | server.handleClient(); //Homepageanfragen versorgen 221 | delay(1); 222 | delay( 5000 ); 223 | } 224 | -------------------------------------------------------------------------------- /CO2-Ampel-DIY-Octopus-SCD30.abp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 562 10 | 373 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 478 19 | 373 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 248 31 | 370 32 | 33 | 414 34 | 188 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 422 44 | 397 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 422 54 | 373 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 248 66 | 1185 67 | 68 | 188 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 392 77 | 1188 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 460 86 | 308 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 686 99 | 308 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 248 108 | 281 109 | 110 | 159 111 | 575 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 460 121 | 284 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 248 130 | 424 131 | 132 | 575 133 | 250 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 382 144 | 687 145 | 146 | 188 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 516 157 | 950 158 | 159 | 208 160 | 224 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 516 170 | 1004 171 | 172 | 221 173 | 227 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 516 183 | 1058 184 | 185 | 224 186 | 236 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 516 196 | 1112 197 | 198 | 227 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 690 208 | 1139 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 690 218 | 1115 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 658 230 | 1085 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 658 240 | 1061 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 658 250 | 1031 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 658 260 | 1007 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 658 269 | 977 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 658 279 | 953 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 517 289 | 723 290 | 291 | 208 292 | 215 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 517 302 | 777 303 | 304 | 212 305 | 218 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 517 315 | 831 316 | 317 | 215 318 | 233 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 517 328 | 885 329 | 330 | 218 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 691 340 | 912 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 691 350 | 888 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 659 363 | 858 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 659 373 | 834 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 659 382 | 804 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 659 392 | 780 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 659 401 | 750 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 659 411 | 726 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 520 421 | 690 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 623 435 | 693 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 530 445 | 693 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 383 455 | 460 456 | 457 | 188 458 | 202 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 383 468 | 514 469 | 470 | 199 471 | 205 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 383 481 | 568 482 | 483 | 202 484 | 230 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 383 493 | 622 494 | 495 | 205 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 557 505 | 649 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 557 515 | 625 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 525 527 | 595 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 525 537 | 571 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 525 547 | 541 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 525 557 | 517 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 525 566 | 487 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 525 576 | 463 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 386 585 | 427 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 489 599 | 430 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 396 609 | 430 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 249 618 | 108 619 | 620 | 159 621 | 169 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 249 631 | 162 632 | 633 | 166 634 | 172 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 249 644 | 216 645 | 646 | 169 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 391 655 | 243 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 391 665 | 219 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 391 674 | 189 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 391 684 | 165 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 391 693 | 135 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 391 703 | 111 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 100 712 | 100 713 | 714 | 715 | 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | -------------------------------------------------------------------------------- /co2_esp8266_feather_scd30_neo_web.abp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 248 9 | 901 10 | 11 | 454 12 | 244 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 248 36 | 1362 37 | 38 | 246 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 392 47 | 1365 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 471 56 | 1293 57 | 58 | 246 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 745 67 | 1296 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 474 77 | 1264 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 474 87 | 1240 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 474 97 | 1216 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 474 107 | 1168 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 474 117 | 1144 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 474 127 | 1120 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 474 137 | 1096 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 474 147 | 1072 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 474 157 | 1048 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 474 167 | 1024 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 474 177 | 1000 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 474 187 | 976 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 474 197 | 952 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 474 207 | 928 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 474 217 | 904 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 530 227 | 734 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 396 237 | 633 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 460 246 | 309 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 652 258 | 309 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 248 268 | 282 269 | 270 | 175 271 | 180 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 460 281 | 285 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 248 291 | 627 292 | 293 | 180 294 | 246 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 382 305 | 728 306 | 307 | 454 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 516 318 | 829 319 | 320 | 461 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 646 330 | 856 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 646 339 | 832 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 517 349 | 764 350 | 351 | 461 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 647 361 | 791 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 647 370 | 767 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 520 380 | 731 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 623 394 | 734 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 383 404 | 663 405 | 406 | 454 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 513 416 | 690 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 513 425 | 666 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 386 435 | 630 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 489 449 | 633 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 516 459 | 555 460 | 461 | 187 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 646 470 | 582 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 646 480 | 558 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 517 490 | 490 491 | 492 | 187 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 647 501 | 517 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 647 511 | 493 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 383 520 | 389 521 | 522 | 180 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 513 531 | 416 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 513 541 | 392 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 249 550 | 108 551 | 552 | 174 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 427 562 | 135 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 427 572 | 111 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 382 582 | 454 583 | 584 | 180 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 520 595 | 457 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 623 609 | 460 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 530 619 | 460 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 489 629 | 359 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 396 639 | 359 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 386 648 | 356 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 248 661 | 353 662 | 663 | 450 664 | 454 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 460 674 | 220 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 686 687 | 220 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 248 696 | 193 697 | 698 | 174 699 | 450 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 460 709 | 196 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 100 718 | 100 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | -------------------------------------------------------------------------------- /CO2-Ampel-DIY-Octopus-SCD30-WEB-AP-CAL.abp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 1173 10 | 465 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 971 19 | 438 20 | 21 | 771 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 1170 40 | 780 41 | 42 | 753 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 1444 51 | 783 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 1173 61 | 751 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 1173 71 | 703 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 1173 80 | 644 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 1399 92 | 644 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 1173 102 | 620 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 1173 111 | 561 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 1399 123 | 561 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 1173 133 | 537 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 1173 143 | 513 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 1173 153 | 489 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 1173 163 | 441 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 852 173 | 430 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 248 183 | 498 184 | 185 | 575 186 | 188 187 | 188 | 189 | 190 | 249 191 | 108 192 | 193 | 159 194 | 166 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 427 204 | 135 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 427 214 | 111 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 562 224 | 447 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 478 233 | 447 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 248 245 | 444 246 | 247 | 414 248 | 762 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 422 258 | 471 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 422 268 | 447 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 248 280 | 1283 281 | 282 | 188 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 392 291 | 1286 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 460 300 | 382 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 686 313 | 382 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 248 322 | 355 323 | 324 | 159 325 | 575 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 460 335 | 358 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 248 344 | 522 345 | 346 | 762 347 | 250 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 382 358 | 785 359 | 360 | 188 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 516 371 | 1048 372 | 373 | 208 374 | 224 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 516 384 | 1102 385 | 386 | 221 387 | 227 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 516 397 | 1156 398 | 399 | 224 400 | 236 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 516 410 | 1210 411 | 412 | 227 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 690 422 | 1237 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 690 432 | 1213 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 658 444 | 1183 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 658 454 | 1159 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 658 464 | 1129 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 658 474 | 1105 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 658 483 | 1075 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 658 493 | 1051 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 517 503 | 821 504 | 505 | 208 506 | 215 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 517 516 | 875 517 | 518 | 212 519 | 218 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 517 529 | 929 530 | 531 | 215 532 | 233 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 517 542 | 983 543 | 544 | 218 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 691 554 | 1010 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 691 564 | 986 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 659 577 | 956 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 659 587 | 932 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 659 596 | 902 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 659 606 | 878 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 659 615 | 848 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 659 625 | 824 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 520 635 | 788 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 623 649 | 791 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 530 659 | 791 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 383 669 | 558 670 | 671 | 188 672 | 202 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 383 682 | 612 683 | 684 | 199 685 | 205 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 383 695 | 666 696 | 697 | 202 698 | 230 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 383 707 | 720 708 | 709 | 205 710 | 711 | 712 | 713 | 714 | 715 | 716 | 717 | 718 | 557 719 | 747 720 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 557 729 | 723 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 525 741 | 693 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 525 751 | 669 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 525 761 | 639 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 525 771 | 615 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 525 780 | 585 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 525 790 | 561 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 386 799 | 525 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 489 813 | 528 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 396 823 | 528 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 249 832 | 181 833 | 834 | 750 835 | 169 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 249 845 | 235 846 | 847 | 166 848 | 172 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 249 858 | 289 859 | 860 | 169 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 391 869 | 316 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 391 879 | 292 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 391 888 | 262 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 391 898 | 238 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 391 907 | 208 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 391 917 | 184 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 100 926 | 100 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | --------------------------------------------------------------------------------