Compare commits

..

No commits in common. "main" and "citizens_brunch_omnom" have entirely different histories.

5 changed files with 1726 additions and 288 deletions

View file

@ -42,11 +42,6 @@ Eine RGB-LED zeigt rot, gelb oder grün, je nach Messwert.
Wir benutzen die PlatformIO IDE. Dort kann das Projekt geöffnet und mit Klick auf "Upload" compiliert und auf den ESP32 geladen werden. Wir benutzen die PlatformIO IDE. Dort kann das Projekt geöffnet und mit Klick auf "Upload" compiliert und auf den ESP32 geladen werden.
Alternativ kann die Datei [unhb-co2-ampel-v0.5.bin](https://git.unhb.de/smash/ebk-unhb-co2ampel/releases/tag/v0.5) hier runtergeladen werden und z.B. mit dem esptool.py mit
esptool.py write_flash 0x0000 unhb-co2-ampel-v0.5.bin
auf dem Mikrocontroller programmiert werden.
## Wiring ## Wiring

File diff suppressed because it is too large Load diff

View file

@ -12,9 +12,8 @@
platform = espressif32 platform = espressif32
board = esp32dev board = esp32dev
framework = arduino framework = arduino
upload_port = /dev/ttyUSB0
lib_deps = lib_deps =
adafruit/Adafruit NeoPixel@^1.6.0 adafruit/Adafruit NeoPixel@^1.6.0
wifwaf/MH-Z19@^1.5.2
squix78/ESP8266_SSD1306@^4.1.0 squix78/ESP8266_SSD1306@^4.1.0
yiannisbourkelis/Uptime Library @ ^1.0.0
wifwaf/MH-Z19@^1.5.3
monitor_speed = 115200

View file

@ -2,338 +2,116 @@
#include "MHZ19.h" #include "MHZ19.h"
#include "SSD1306Wire.h" #include "SSD1306Wire.h"
#include <Adafruit_NeoPixel.h> #include <Adafruit_NeoPixel.h>
#include "fonts-custom.h"
#include <Preferences.h>
#include "uptime_formatter.h"
// Grenzwerte für die CO2 Werte für grün und gelb, alles überhalb davon bedeutet rot // Maximum CO² levels for green and yellow, everything above is considered red.
#define GREEN_CO2 800 #define GREEN_CO2 800
#define YELLOW_CO2 1000 #define YELLOW_CO2 1000
// CO2 Mess-Intervall in Milisekunden // Measurement interval in miliseconds
#define CO2_INTERVAL 15*1000 #define INTERVAL 60000
// Display Update-Intervall in Milisekunden
#define DISPLAY_INTERVAL 2500
// Dauer der Kalibrierungsphase in Milisekunden
#define CAL_INTERVAL 180*1000
// Boot-Mode Konstanten // Pins for MH-Z19
#define BOOT_NORMAL 42
#define BOOT_CALIBRATE 23
#define BOOT_UNKNOWN 69
// Pins für den MH-Z19b
#define RX_PIN 16 #define RX_PIN 16
#define TX_PIN 17 #define TX_PIN 17
// Pins für das SD1306 OLED-Display // Pins for SD1306
#define SDA_PIN 21 #define SDA_PIN 21
#define SCL_PIN 22 #define SCL_PIN 22
// Pin für den LED-Ring // Pin for LED
#define LED_PIN 4 #define LED_PIN 4
// Anzahl der angeschlossenen LEDs am Ring // number of LEDs connected
#define NUMPIXELS 8 #define NUMPIXELS 8
Preferences preferences;
MHZ19 myMHZ19; MHZ19 myMHZ19;
HardwareSerial mySerial(1); HardwareSerial mySerial(1);
SSD1306Wire display(0x3c, SDA_PIN, SCL_PIN); SSD1306Wire display(0x3c, SDA_PIN, SCL_PIN);
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, LED_PIN, NEO_GRB + NEO_KHZ800); Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, LED_PIN, NEO_RGB + NEO_KHZ800);
String ampelversion = "0.50";
unsigned long getDataTimer = 0;
int lastvals[120]; int lastvals[120];
int dheight; int dheight;
int currentBootMode;
void setBootMode(int bootMode) {
if(bootMode == BOOT_NORMAL) {
Serial.println("Startmodus nächster Reboot: Messmodus");
preferences.putUInt("cal", bootMode);
if (bootMode != preferences.getUInt("cal", BOOT_UNKNOWN)) Serial.println("Konnte neuen Bootmodus nicht schreiben :-(");
}
else if(bootMode == BOOT_CALIBRATE) {
Serial.println("Startmodus nächster Reboot: Kalibrierungsmodus");
preferences.putUInt("cal", bootMode);
if (bootMode != preferences.getUInt("cal", BOOT_UNKNOWN)) Serial.println("Konnte neuen Bootmodus nicht schreiben :-(");
} else {
Serial.println("Unerwarteter Boot-Mode soll gespeichert werden. Abgebrochen.");
}
}
void toggleBootMode(int bootMode) {
switch (bootMode){
case BOOT_CALIBRATE:
setBootMode(BOOT_NORMAL); break;
case BOOT_NORMAL:
setBootMode(BOOT_CALIBRATE); break;
case BOOT_UNKNOWN:
Serial.println("Bootmode Unbekannt! Neue Ampel? Nächster Start wird Messmodus.");
setBootMode(BOOT_NORMAL); break;
default:
Serial.print("Unerwarteter Bootmode-Wert: "); Serial.println(bootMode);
Serial.println("Nächster Start wird Messmodus.");
setBootMode(BOOT_NORMAL); break;
}
}
int readCO2(){
static int co2=400;
static unsigned long getDataTimer = 0;
if (millis() - getDataTimer >= CO2_INTERVAL) {
// Neuen CO2 Wert lesen
co2 = myMHZ19.getCO2();
// Alle Werte in der Messwertliste um eins verschieben
for (int x = 1; x <= 119; x = x + 1) {
lastvals[x - 1] = lastvals[x];
}
// Aktuellen Messer am Ende einfügen
lastvals[119] = co2;
// Ein wenig Debug-Ausgabe
Serial.print("Neue Messung - Aktueller CO2-Wert: ");
Serial.print(co2);
Serial.print("; Background CO2: " + String(myMHZ19.getBackgroundCO2()));
Serial.print("; Temperatur: " + String(myMHZ19.getTemperature()) + " Temperature Adjustment: " + String(myMHZ19.getTempAdjustment()));
Serial.println("; uptime: " + uptime_formatter::getUptime());
getDataTimer = millis();
}
return co2;
}
void setup() { void setup() {
Serial.begin(115200); Serial.begin(9600);
Serial.println("Starte...");
Serial.print("CO2-Ampel Firmware: ");Serial.println(ampelversion);
// Ab hier Bootmodus initialisieren und festlegen
preferences.begin("co2", false);
currentBootMode = preferences.getUInt("cal", BOOT_UNKNOWN); // Aktuellen Boot-Mode lesen und speichern
switch(currentBootMode){
case BOOT_CALIBRATE:
Serial.println("Startmodus Aktuell: Kalibrierungsmodus");
toggleBootMode(currentBootMode); // beim nächsten boot ggfs. im anderen modus starten, wird später nach 10 Sekunden zurückgesetzt
break;
case BOOT_NORMAL:
Serial.println("Startmodus Aktuell: Messmodus");
toggleBootMode(currentBootMode); // beim nächsten boot ggfs. im anderen modus starten, wird später nach 10 Sekunden zurückgesetzt
break;
default:
Serial.println("Startmodus Aktuell: Unbekannt oder Ungültig");
Serial.println("Nächster Start im Messmodus");
setBootMode(BOOT_NORMAL);
break;
}
// Ab hier Display einrichten
display.init();
// display.setLogBuffer(5,30);
display.setContrast(255);
delay(500);
display.clear();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 0, "Version: " + String(ampelversion));
if(currentBootMode == BOOT_NORMAL) {
display.drawString(64, 17, "Zum Kalibrieren");
display.drawString(64, 34, "jetzt Neustarten" );
} else {
display.drawString(64, 17, "Zum Messen");
display.drawString(64, 34, "jetzt Neustarten" );
}
display.display();
dheight = display.getHeight();
// Ab hier Sensor einrichten
mySerial.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN); mySerial.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
myMHZ19.begin(mySerial); myMHZ19.begin(mySerial);
myMHZ19.autoCalibration(false); // "Automatic Baseline Calibration" (ABC) erstmal aus pixels.clear();
char myVersion[4]; display.init();
myMHZ19.getVersion(myVersion); display.setContrast(255);
Serial.print("\nMH-Z19b Firmware Version: "); delay(1000);
Serial.print(myVersion[0]);Serial.print(myVersion[1]);;Serial.print(".");Serial.print(myVersion[2]);Serial.println(myVersion[3]); display.clear();
Serial.print("Range: "); Serial.println(myMHZ19.getRange()); display.flipScreenVertically();
Serial.print("Background CO2: "); Serial.println(myMHZ19.getBackgroundCO2()); dheight = display.getHeight();
Serial.print("Temperature Cal: "); Serial.println(myMHZ19.getTempAdjustment()); myMHZ19.autoCalibration();
Serial.print("ABC Status: "); myMHZ19.getABC() ? Serial.println("ON") : Serial.println("OFF"); // Fill array of last measurements with -1
Serial.print("read EEPROM value: "); Serial.println(currentBootMode);
Serial.print("First CO2 value (should be 400): "); Serial.println(readCO2());
// Liste der Messwerte mit "-1" befüllen ("-1" wird beinm Graph nicht gezeichnet)
for (int x = 0; x <= 119; x = x + 1) { for (int x = 0; x <= 119; x = x + 1) {
lastvals[x] = -1; lastvals[x] = -1;
} }
// Ab hier LED-Ring konfigurien
pixels.begin(); pixels.begin();
pixels.clear(); for(int i=0; i<NUMPIXELS; i++) {
pixels.fill(pixels.Color(0,0,0)); pixels.setPixelColor(i, 0,0,50);
pixels.show(); pixels.show();
}
// Wir lesen schonmal einen CO2 Sensorwert, da die erste Werte meist Müll sind
delay(5000);
Serial.print("Second CO2 value: "); Serial.println(readCO2());
Serial.flush();
} }
int calc_vpos_for_co2(int co2val, int max_height) { int calc_vpos_for_co2(int co2val, int display_height) {
return int((float(max_height) / (5000-350)) * (co2val-350)); return display_height - int((float(display_height) / 3000) * co2val);
} }
void set_led_color(int co2) { void set_led_color(int co2) {
static char blinkState=0;
static signed char blinkDirection=1;
static int blinkOn=0;
static int blinkOff=0;
if (co2 < GREEN_CO2) { if (co2 < GREEN_CO2) {
pixels.fill(pixels.Color(0,0,0)); // Grün // Green
pixels.setPixelColor(4,pixels.Color(0,2,0));
} else if (co2 < YELLOW_CO2) {
pixels.fill(pixels.Color(50,30,0)); // Gelb
} else {
blinkState+=blinkDirection;
if( (blinkState<90) & (blinkState>0) ) {
pixels.fill(pixels.Color(blinkState,00,0));
}
else if (blinkState==90) {
blinkDirection=0; blinkOn++;
if(blinkOn==400) {
blinkOn=0; blinkDirection=-1; blinkState=89;
}
}
else if (blinkState==0) {
blinkDirection=0; blinkOff++;
if(blinkOff==50) {
blinkOff=0; blinkDirection=1; blinkState=1;
}
}
}
pixels.show();
delay(10);
}
void rainbow(int wait) {
for(long firstPixelHue = 0; firstPixelHue < 65536; firstPixelHue += 256) {
for(int i=0; i<NUMPIXELS; i++) { for(int i=0; i<NUMPIXELS; i++) {
int pixelHue = firstPixelHue + (i * 65536L / NUMPIXELS); pixels.setPixelColor(i, 30,0,0);
pixels.setPixelColor(i, pixels.gamma32(pixels.ColorHSV(pixelHue))); }
} else if (co2 < YELLOW_CO2) {
// Yellow
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, 40,40,0);
}
} else {
// Red
for(int i=0; i<NUMPIXELS; i++) {
pixels.setPixelColor(i, 0,90,0);
}
} }
pixels.show(); pixels.show();
delay(wait);
}
} }
void calibrateCO2() { void loop() {
display.setTextAlignment(TEXT_ALIGN_CENTER); if (millis() - getDataTimer >= INTERVAL) {
display.setFont(ArialMT_Plain_16); // Get new CO² value.
display.clear(); int CO2 = myMHZ19.getCO2();
display.drawString(64, 0, "! Kalibriere !"); // Shift entries in array back one position.
display.setFont(ArialMT_Plain_24); for (int x = 1; x <= 119; x = x + 1) {
display.drawString(64, 18, "NICHT"); lastvals[x - 1] = lastvals[x];
display.setFont(ArialMT_Plain_16);
display.drawString(64, 44, "Neustarten");
display.display();
Serial.println("Kalibrierung startet nun");
myMHZ19.setRange(5000);
delay(500);
myMHZ19.calibrate();
delay(500);
myMHZ19.autoCalibration(false);
delay(500);
display.clear();
display.setFont(ArialMT_Plain_24);
display.drawString(64, 0, "Fertig!");
display.display();
setBootMode(BOOT_NORMAL);
delay(10000);
display.clear();
} }
// Add new measurement at the end.
void updateDisplayCO2(int co2) { lastvals[119] = CO2;
static unsigned long getUpdateTimer = 0; // Clear display and redraw whole graph.
if (millis() - getUpdateTimer >= DISPLAY_INTERVAL) {
// Display löschen und alles neu schreiben/zeichnen
display.clear(); display.clear();
for (int h = 1; h < 120; h = h + 1) { for (int h = 1; h < 120; h = h + 1) {
int curval = lastvals[h]; int curval = lastvals[h];
if (curval > 0) { if (curval > 0) {
int vpos = 63 - calc_vpos_for_co2(lastvals[h], 16); int vpos = calc_vpos_for_co2(lastvals[h], dheight);
int vpos_last = 63 - calc_vpos_for_co2(lastvals[h - 1], 16); int vpos_last = calc_vpos_for_co2(lastvals[h - 1], dheight);
display.drawLine(h - 1, vpos_last, h, vpos); display.drawLine(h - 1, vpos_last, h, vpos);
} }
} }
// Aktuellen CO2 Wert ausgeben // Set LED color and print value on display
set_led_color(CO2);
display.setLogBuffer(1, 30);
display.setFont(Cousine_Regular_54); display.setFont(Cousine_Regular_54);
display.setTextAlignment(TEXT_ALIGN_CENTER); display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64 ,0 , String(co2)); display.drawString(64 ,0 , String(CO2));
display.drawLogBuffer(0, 0);
display.display(); display.display();
// Debug output
// Fertig mit update; Zeitpunkt für das nächste Update speichern Serial.print("CO2 (ppm): ");
getUpdateTimer = millis(); Serial.println(CO2);
getDataTimer = millis();
} }
} }
void loop() {
static unsigned long calibrationStart = 0;
static int countdown = 0;
static int safezone = false;
int co2;
// Nur für die ersten 10 Sekunden wichtig,
if ( (!safezone) & (millis() > 10000) ) {
Serial.println("=== 10 Sekunden im Betrieb, nächster Boot im Messmodus ===");
setBootMode(BOOT_NORMAL);
safezone = true;
}
if (safezone) {
if (currentBootMode == BOOT_CALIBRATE){
if (millis() - calibrationStart <= CAL_INTERVAL) {
rainbow(10);
countdown = ((calibrationStart + CAL_INTERVAL) - millis()) / 1000;
Serial.println("Countdown: " + String(countdown));
display.clear();
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.drawString(0, 0, "Kalibrierung");
display.setFont(ArialMT_Plain_10);;
display.drawString(0, 17, "Abbrechen durch Neustart");
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.setFont(ArialMT_Plain_16);;
display.drawString(64, 35, "Noch: " + String(countdown) + " Sek.");
display.display();
}
else if (millis() - calibrationStart >= CAL_INTERVAL) {
calibrateCO2();
currentBootMode = BOOT_NORMAL; //Fertig, ab jetzt kann es normal weitergehen
}
} else {
// Achtung: readCO2() liefer nur alle "INTERVAL" ms ein neuen Wert, der alte wird aber zwischengespeichert
co2 = readCO2();
// Update Display
updateDisplayCO2(co2);
// Farbe des LED-Rings setzen
if(currentBootMode == BOOT_NORMAL) { set_led_color(co2); }
}
}
}