From 56d4912e871deed3580b08bba2a5d352be6ba69e Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Mon, 16 Nov 2020 23:48:15 +0100 Subject: [PATCH] First commit --- LICENSE | 3 + operame.ino | 242 +++++++++++++++++++++++++++++++++++++++++++++++++ platformio.ini | 65 +++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 LICENSE create mode 100644 operame.ino create mode 100644 platformio.ini diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5f69e02 --- /dev/null +++ b/LICENSE @@ -0,0 +1,3 @@ +Pick your favourite OSI approved license :) + +http://www.opensource.org/licenses/alphabetical diff --git a/operame.ino b/operame.ino new file mode 100644 index 0000000..42e1a78 --- /dev/null +++ b/operame.ino @@ -0,0 +1,242 @@ +#define Sprintf(f, ...) ({ char* s; asprintf(&s, f, __VA_ARGS__); String r = s; free(s); r; }) +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +unsigned long mqtt_interval; +const int portalbutton = 35; +const int demobutton = 0; +bool ota_enabled; +int co2_warning; +int co2_critical; +int co2_blink; + +MQTTClient mqtt; +HardwareSerial hwserial1(1); +TFT_eSPI display; +TFT_eSprite sprite(&display); +MHZ19 mhz; +String mqtt_topic; +String mqtt_template; +bool add_units; +bool wifi_enabled; +bool mqtt_enabled; +int max_failures; + +void retain(String topic, String message) { + Serial.printf("%s %s\n", topic.c_str(), message.c_str()); + mqtt.publish(topic, message, true, 0); +} + +void display_big(const String& text, int fg = TFT_WHITE, int bg = TFT_BLACK) { + sprite.setTextSize(1); + bool nondigits = false; + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (c < '0' || c > '9') nondigits = true; + } + sprite.setTextFont(nondigits ? 4 : 8); + sprite.setTextSize(nondigits && text.length() < 10 ? 2 : 1); + sprite.setTextDatum(MC_DATUM); + sprite.setTextColor(fg, bg); + sprite.fillSprite(bg); + if (WiFi.status() == WL_CONNECTED) + sprite.drawRect(0, 0, display.width(), display.height(), TFT_BLUE); + sprite.drawString(text, display.width()/2, display.height()/2); + + sprite.pushSprite(0, 0); +} + + +void setup_ota() { + ArduinoOTA.setHostname(WiFiSettings.hostname.c_str()); + ArduinoOTA.setPassword(WiFiSettings.password.c_str()); + ArduinoOTA.onStart( []() { display_big("OTA", TFT_BLUE); }); + ArduinoOTA.onEnd( []() { display_big("OTA done", TFT_GREEN); }); + ArduinoOTA.onError( [](ota_error_t e) { display_big("OTA failed", TFT_RED); }); + ArduinoOTA.onProgress([](unsigned int p, unsigned int t) { + String pct { (int) ((float) p / t * 100) }; + display_big(pct + "%"); + }); + ArduinoOTA.begin(); +} + +void check_portalbutton() { + if (digitalRead(portalbutton)) return; + delay(50); + if (digitalRead(portalbutton)) return; + WiFiSettings.portal(); +} + +void check_demobutton() { + if (digitalRead(demobutton)) return; + delay(50); + if (digitalRead(demobutton)) return; + ppm_demo(); +} + +void check_buttons() { + check_portalbutton(); + check_demobutton(); +} + +void display_ppm(int ppm) { + int fg, bg; + if (ppm >= co2_critical) { + fg = TFT_WHITE; + bg = TFT_RED; + } else if (ppm >= co2_warning) { + fg = TFT_BLACK; + bg = TFT_YELLOW; + } else { + fg = TFT_GREEN; + bg = TFT_BLACK; + } + + if (ppm >= co2_blink && millis() % 2000 < 1000) { + std::swap(fg, bg); + } + display_big(String(ppm), fg, bg); +} + +void ppm_demo() { + display_big("demo!"); + delay(3000); + for (int p = 400; p < 1200; p++) { + display_ppm(p); + delay(30); + } +} + +void setup() { + Serial.begin(115200); + Serial.println("Operame start"); + SPIFFS.begin(true); + pinMode(portalbutton, INPUT_PULLUP); + pinMode(demobutton, INPUT_PULLUP); + pinMode(4, OUTPUT); + digitalWrite(4, HIGH); + + display.init(); + display.fillScreen(TFT_BLACK); + display.setRotation(1); + sprite.createSprite(display.width(), display.height()); + + hwserial1.begin(9600, SERIAL_8N1, 22, 21); + mhz.begin(hwserial1); + display_big("operame"); + delay(1000); + check_sensor(); + mhz.setFilter(true, true); + mhz.autoCalibration(); + + WiFiSettings.hostname = "operame-"; + wifi_enabled = WiFiSettings.checkbox("operame_wifi", false, "WiFi-verbinding gebruiken"); + ota_enabled = WiFiSettings.checkbox("operame_ota", false, "Draadloos herprogrammeren inschakelen. (Gebruikt portaalwachtwoord!)") && wifi_enabled; + + WiFiSettings.heading("CO2-niveaus"); + co2_warning = WiFiSettings.integer("operame_co2_warning", 400, 5000, 700, "Geel vanaf [ppm]"); + co2_critical = WiFiSettings.integer("operame_co2_critical",400, 5000, 800, "Rood vanaf [ppm]"); + co2_blink = WiFiSettings.integer("operame_co2_blink", 800, 5000, 800, "Knipperen vanaf [ppm]"); + + WiFiSettings.heading("MQTT"); + mqtt_enabled = WiFiSettings.checkbox("operama_mqtt", false, "Metingen via het MQTT-protocol versturen") && wifi_enabled; + String server = WiFiSettings.string("mqtt_server", 64, "", "Broker"); + int port = WiFiSettings.integer("mqtt_port", 0, 65535, 1883, "Broker TCP-poort"); + max_failures = WiFiSettings.integer("operame_max_failures", 0, 1000, 10, "Aantal verbindingsfouten voor automatische herstart"); + mqtt_topic = WiFiSettings.string("operame_mqtt_topic", WiFiSettings.hostname, "Topic"); + mqtt_interval = 1000UL * WiFiSettings.integer("operame_mqtt_interval", 10, 3600, 60, "Publicatie-interval [s]"); + mqtt_template = WiFiSettings.string("operame_mqtt_template", "{} PPM", "Berichtsjabloon"); + WiFiSettings.info("De {} in het sjabloon wordt vervangen door de gemeten waarde."); + + if (ota_enabled) WiFiSettings.onPortal = setup_ota; + + WiFiSettings.onConnect = []() { + check_buttons(); + display_big("Verbinden met WiFi...", TFT_BLUE); + return 50; + }; + WiFiSettings.onFailure = []() { + display_big("WiFi mislukt!", TFT_RED); + delay(2000); + }; + WiFiSettings.onPortal = []() { + display_big("Configuratieportal", TFT_BLUE); + }; + WiFiSettings.onPortalWaitLoop = []() { + if (ota_enabled) ArduinoOTA.handle(); + }; + + if (wifi_enabled) WiFiSettings.connect(false, 15); + + static WiFiClient wificlient; + if (mqtt_enabled) mqtt.begin(server.c_str(), port, wificlient); + + if (ota_enabled) setup_ota(); + + display_big(":-)"); +} + +void connect_mqtt() { + if (mqtt.connected()) return; // already/still connected + + static int failures = 0; + if (mqtt.connect(WiFiSettings.hostname.c_str())) { + failures = 0; + } else { + failures++; + if (failures >= max_failures) ESP.restart(); + } +} + +void check_sensor() { + if (mhz.errorCode == RESULT_OK) return; + while (1) { + delay(1000); + mhz.verify(); + if (mhz.errorCode == RESULT_OK) return; + display_big("sensorfout", TFT_RED); + } +} + +void loop() { + unsigned long next = millis() + 6000; + static unsigned long next_mqtt = 0; + + if (mqtt_enabled) mqtt.loop(); + + int CO2 = mhz.getCO2(); + check_sensor(); + + Serial.println(CO2); + + if (CO2) { + display_ppm(CO2); + + if (mqtt_enabled && millis() > next_mqtt) { + connect_mqtt(); + String message = mqtt_template; + message.replace("{}", String(CO2)); + retain(mqtt_topic, message); + next_mqtt = millis() + mqtt_interval; + } + } else { + display_big("wacht..."); + } + + while (millis() < next) { + if (CO2) display_ppm(CO2); // repeat, for blinking + if (ota_enabled) ArduinoOTA.handle(); + check_buttons(); + delay(20); + } + Serial.println(esp_get_free_heap_size()); +} diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..883a0c9 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,65 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + +[platformio] +src_dir = . +default_envs = serial +extra_configs = platformio-*.ini + +[env] +platform = espressif32 +board = esp32thing +board_build.partitions = default.csv +framework = arduino +targets = upload +monitor_speed = 115200 +#upload_speed = 460800 +lib_deps = + ESP-WiFiSettings @^3.5 + MH-Z19 + TFT_eSPI +# MQTT +build_flags = + -DUSER_SETUP_LOADED=1 + -DST7789_DRIVER=1 + -DCGRAM_OFFSET=1 + -DTFT_WIDTH=135 + -DTFT_HEIGHT=240 + -DTFT_MOSI=19 + -DTFT_SCLK=18 + -DTFT_CS=5 + -DTFT_DC=16 + -DTFT_RST=-23 + -DTFT_BL=4 + -DTFT_BACKLIGHT_ON=HIGH + -DLOAD_GLCD=1 + -DLOAD_FONT2=1 + -DLOAD_FONT4=1 + -DLOAD_FONT6=1 + -DLOAD_FONT7=1 + -DLOAD_FONT8=1 + -DLOAD_GFXFF=1 + -DSPI_FREQUENCY=40000000 + +[env:serial] +upload_protocol = esptool + +[env:ota] +upload_protocol = espota +upload_port = snuffelaar-HEX_HERE.local +upload_flags = + --port=3232 + --auth=PASSWORD_HERE +; Alternatively, instead of editing this file (which is annoying because it +; might end up being committed in git), you can create extra an extra config +; file. +; Just copy the [env:ota] section to a new file called platformio-NAME.ini +; and change [env:ota] to [env:NAME]. You can use this to OTA-update multiple +; Snuffelaars with a single command: pio run -t upload -e NAME -e NAME -e NAME