From 216534d105026aa8826bce0732a6af5b8bc9842a Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Wed, 30 Dec 2020 16:45:52 +0100 Subject: [PATCH 01/10] Fix bug: OTA not enabled in portal Thanks, Bertrik! --- operame.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operame.ino b/operame.ino index 79f26f5..29d36ad 100644 --- a/operame.ino +++ b/operame.ino @@ -329,7 +329,7 @@ void setup() { static int portal_phase = 0; static unsigned long portal_start; WiFiSettings.onPortal = [] { - if (ota_enabled) setup_ota; + if (ota_enabled) setup_ota(); portal_start = millis(); }; WiFiSettings.onPortalView = [] { From 3f87f639512c38c8d8cbf26ddfd4ce9d965cfdc3 Mon Sep 17 00:00:00 2001 From: Bertrik Sikken Date: Wed, 30 Dec 2020 16:58:47 +0100 Subject: [PATCH 02/10] Clarify library dependencies in platformio.ini by specififying them as author/libname. --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 6987ca4..4fd7db6 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,10 +21,10 @@ framework = arduino targets = upload monitor_speed = 115200 lib_deps = - ESP-WiFiSettings @^3.6 - MH-Z19 - TFT_eSPI - MQTT + juerd/ESP-WiFiSettings@^3.6 + wifwaf/MH-Z19 + bodmer/TFT_eSPI + 256dpi/MQTT build_flags = # -DCORE_DEBUG_LEVEL=5 From 7fb7682cfe1da1138a6c0138cbee896efd383615 Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Thu, 31 Dec 2020 01:06:04 +0100 Subject: [PATCH 03/10] Rollback #2 / 3f87f63 Doesn't work. --- platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 4fd7db6..473bce0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,10 +21,10 @@ framework = arduino targets = upload monitor_speed = 115200 lib_deps = - juerd/ESP-WiFiSettings@^3.6 - wifwaf/MH-Z19 - bodmer/TFT_eSPI - 256dpi/MQTT + ESP-WiFiSettings@^3.6 + MH-Z19 + TFT_eSPI + MQTT build_flags = # -DCORE_DEBUG_LEVEL=5 From d1942fcaa0f41e0242842abd9f17ac0ca7384199 Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Sat, 2 Jan 2021 19:19:25 +0100 Subject: [PATCH 04/10] More bits to support >1 minute MQTT intervals Oops, overlooked that 16 when copy/pasting the macro. --- operame.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operame.ino b/operame.ino index 29d36ad..f9128b9 100644 --- a/operame.ino +++ b/operame.ino @@ -394,7 +394,7 @@ void setup() { if (ota_enabled) setup_ota(); } -#define every(t) for (static uint16_t _lasttime; (uint16_t)((uint16_t)millis() - _lasttime) >= (t); _lasttime = millis()) +#define every(t) for (static unsigned long _lasttime; (unsigned long)((unsigned long)millis() - _lasttime) >= (t); _lasttime = millis()) void loop() { static int co2; From f76e092998a63391707a88b592c4e5cc465ec2ec Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Tue, 26 Jan 2021 05:09:52 +0100 Subject: [PATCH 05/10] Use Dutch translation of WiFiSettings --- operame.ino | 3 +++ platformio.ini | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/operame.ino b/operame.ino index f9128b9..c3ec65c 100644 --- a/operame.ino +++ b/operame.ino @@ -9,6 +9,8 @@ #include #include +#define LANGUAGE "nl" + enum Driver { AQC, MHZ }; Driver driver; MQTTClient mqtt; @@ -298,6 +300,7 @@ void setup() { delay(2000); WiFiSettings.hostname = "operame-"; + WiFiSettings.language = LANGUAGE; wifi_enabled = WiFiSettings.checkbox("operame_wifi", false, "WiFi-verbinding gebruiken"); ota_enabled = WiFiSettings.checkbox("operame_ota", false, "Draadloos herprogrammeren inschakelen. (Gebruikt portaalwachtwoord!)") && wifi_enabled; diff --git a/platformio.ini b/platformio.ini index 473bce0..927db8a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,7 @@ framework = arduino targets = upload monitor_speed = 115200 lib_deps = - ESP-WiFiSettings@^3.6 + ESP-WiFiSettings@^3.7 MH-Z19 TFT_eSPI MQTT From 69ddf8054a727c71153b3ba200067794de13df5d Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Tue, 26 Jan 2021 06:28:14 +0100 Subject: [PATCH 06/10] Add basic i18n support and English translations --- README.md | 6 ++ operame.ino | 89 ++++++++++----------------- operame_strings.h | 151 ++++++++++++++++++++++++++++++++++++++++++++++ platformio.ini | 7 ++- 4 files changed, 195 insertions(+), 58 deletions(-) create mode 100644 operame_strings.h diff --git a/README.md b/README.md index 3fd7efe..a3b7a93 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ Dit is de broncode van de firmware voor de [Operame CO2-meter](https://operame.nl/). +## Language + +The default language is Dutch; users can pick a different language using the +WiFi configuration portal. To change the default setting to English, change +`#define LANGUAGE "nl"` to `#define LANGUAGE "en"`. + ## Gebruik ### Installatie diff --git a/operame.ino b/operame.ino index c3ec65c..a8f2fcc 100644 --- a/operame.ino +++ b/operame.ino @@ -8,8 +8,10 @@ #include #include #include +#include #define LANGUAGE "nl" +OperameLanguage::Texts T; enum Driver { AQC, MHZ }; Driver driver; @@ -178,7 +180,7 @@ void connect_mqtt() { failures = 0; } else { failures++; - if (failures >= max_failures) panic("MQTT onbereikbaar"); + if (failures >= max_failures) panic(T.error_mqtt); } } @@ -258,7 +260,7 @@ int get_co2() { if (driver == MHZ) return mhz_get_co2(); // Should be unreachable - panic("driverfout"); + panic(T.error_driver); return -1; // suppress warning } @@ -280,7 +282,7 @@ void setup() { sprite.createSprite(display.width(), display.height()); while (digitalRead(pin_pcb_ok)) { - display_big("module verkeerd om!", TFT_RED); + display_big(T.error_module, TFT_RED); delay(1000); } @@ -301,32 +303,39 @@ void setup() { WiFiSettings.hostname = "operame-"; WiFiSettings.language = LANGUAGE; - 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.begin(); + + OperameLanguage::select(T, WiFiSettings.language); + for (auto& str : T.portal_instructions[0]) { + str.replace("{ssid}", WiFiSettings.hostname); + } + + wifi_enabled = WiFiSettings.checkbox("operame_wifi", false, T.config_wifi); + ota_enabled = WiFiSettings.checkbox("operame_ota", false, T.config_ota) && 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]"); + co2_warning = WiFiSettings.integer("operame_co2_warning", 400, 5000, 700, T.config_co2_warning); + co2_critical = WiFiSettings.integer("operame_co2_critical",400, 5000, 800, T.config_co2_critical); + co2_blink = WiFiSettings.integer("operame_co2_blink", 800, 5000, 800, T.config_co2_blink); WiFiSettings.heading("MQTT"); - mqtt_enabled = WiFiSettings.checkbox("operame_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."); + mqtt_enabled = WiFiSettings.checkbox("operame_mqtt", false, T.config_mqtt) && wifi_enabled; + String server = WiFiSettings.string("mqtt_server", 64, "", T.config_mqtt_server); + int port = WiFiSettings.integer("mqtt_port", 0, 65535, 1883, T.config_mqtt_port); + max_failures = WiFiSettings.integer("operame_max_failures", 0, 1000, 10, T.config_max_failures); + mqtt_topic = WiFiSettings.string("operame_mqtt_topic", WiFiSettings.hostname, T.config_mqtt_topic); + mqtt_interval = 1000UL * WiFiSettings.integer("operame_mqtt_interval", 10, 3600, 60, T.config_mqtt_interval); + mqtt_template = WiFiSettings.string("operame_mqtt_template", "{} PPM", T.config_mqtt_template); + WiFiSettings.info(T.config_template_info); WiFiSettings.onConnect = [] { - display_big("Verbinden met WiFi...", TFT_BLUE); + display_big(T.connecting, TFT_BLUE); check_portalbutton(); return 50; }; WiFiSettings.onFailure = [] { - display_big("WiFi mislukt!", TFT_RED); + display_big(T.error_wifi, TFT_RED); delay(2000); }; static int portal_phase = 0; @@ -345,44 +354,10 @@ void setup() { if (WiFi.softAPgetStationNum() == 0) portal_phase = 0; else if (! portal_phase) portal_phase = 1; - switch (portal_phase) { - case 0: { - display_lines({ - "Voor configuratie,", - "verbind met WiFi", - "\"" + WiFiSettings.hostname + "\"", - "met een smartphone." - }, TFT_WHITE, TFT_BLUE); - break ; - } - case 1: { - display_lines({ - "Volg instructies op", - "uw smartphone.", - "(inlog-notificatie)" - }, TFT_WHITE, TFT_BLUE); - break; - } - case 2: { - display_lines({ - "Wijzig instellingen", - "en klik op \"Save\".", - "(rechtsonder)" - }, TFT_WHITE, TFT_BLUE); - break; - } - case 3: { - display_lines({ - "Wijzig instellingen", - "en klik op \"Save\".", - "Of \"Restart device\"", - "als u klaar bent." - }, TFT_WHITE, TFT_BLUE); - break; - } - } + display_lines(T.portal_instructions[portal_phase], TFT_WHITE, TFT_BLUE); + if (portal_phase == 0 && millis() - portal_start > 10*60*1000) { - panic("Tijd verstreken"); + panic(T.error_timeout); } if (ota_enabled) ArduinoOTA.handle(); @@ -409,9 +384,9 @@ void loop() { every(50) { if (co2 < 0) { - display_big("sensorfout", TFT_RED); + display_big(T.error_sensor, TFT_RED); } else if (co2 == 0) { - display_big("wacht..."); + display_big(T.wait); } else { // some MH-Z19's go to 10000 but the display has space for 4 digits display_ppm(co2 > 9999 ? 9999 : co2); diff --git a/operame_strings.h b/operame_strings.h new file mode 100644 index 0000000..eb4350c --- /dev/null +++ b/operame_strings.h @@ -0,0 +1,151 @@ +#include +#include +#include + +namespace OperameLanguage { + +struct Texts { + const char + *error_mqtt, + *error_driver, + *error_module, + *error_timeout, + *error_sensor, + *error_wifi, + *config_wifi, + *config_ota, + *config_co2_warning, + *config_co2_critical, + *config_co2_blink, + *config_mqtt, + *config_mqtt_server, + *config_mqtt_port, + *config_max_failures, + *config_mqtt_topic, + *config_mqtt_interval, + *config_mqtt_template, + *config_template_info, + *connecting, + *wait + ; + std::vector> portal_instructions; +}; + +std::map languages { + // Ordered alphabetically + { "en", "English" }, + { "nl", "Nederlands" }, +}; + +bool available(const String& language) { + return languages.count(language) == 1; +} + +bool select(Texts& T, String language) { + if (! available(language)) { + if (available("en")) language = "en"; + else language = languages.begin()->first; + } + + if (language == "en") { + T.error_mqtt = "MQTT unreachable"; + T.error_driver = "driver error"; + T.error_module = "module turned around!"; + T.error_timeout = "Time's up"; + T.error_sensor = "sensor error"; + T.error_wifi = "WiFi failed!"; + T.wait = "wait..."; + T.config_wifi = "Use WiFi connection"; + T.config_ota = "Enable wireless reprogramming. (Uses portal password!)"; + T.config_co2_warning = "Yellow from [ppm]"; + T.config_co2_critical = "Red from [ppm]"; + T.config_co2_blink = "Blink from [ppm]"; + T.config_mqtt = "Publish measurements via the MQTT protocol"; + T.config_mqtt_server = "Broker"; // probably should not be translated + T.config_mqtt_port = "Broker TCP port"; + T.config_max_failures = "Number of failed connections before automatic restart"; + T.config_mqtt_topic = "Topic"; // probably should not be translated + T.config_mqtt_interval = "Publication interval [s]"; + T.config_mqtt_template = "Message template"; + T.config_template_info = "The {} in the template is replaced by the measurement value."; + T.connecting = "Connecting to WiFi..."; + T.portal_instructions = { + { + "For configuration,", + "connect to WiFi", + "\"{ssid}\"", + "with a smartphone." + }, + { + "Follow instructions", + "on your smartphone.", + "(log in notification)" + }, + { + "Change settings", + "and click \"Save\".", + "(bottom right)" + }, + { + "Change settings", + "and click \"Save\".", + "Or \"Restart device\"", + "when you're done." + } + }; + return true; + } + + if(language == "nl") { + T.error_mqtt = "MQTT onbereikbaar"; + T.error_driver = "driverfout"; + T.error_module = "module verkeerd om!"; + T.error_timeout = "Tijd verstreken"; + T.error_sensor = "sensorfout"; + T.error_wifi = "WiFi mislukt!"; + T.wait = "wacht..."; + T.config_wifi = "WiFi-verbinding gebruiken"; + T.config_ota = "Draadloos herprogrammeren inschakelen. (Gebruikt portaalwachtwoord!)"; + T.config_co2_warning = "Geel vanaf [ppm]"; + T.config_co2_critical = "Rood vanaf [ppm]"; + T.config_co2_blink = "Knipperen vanaf [ppm]"; + T.config_mqtt = "Metingen via het MQTT-protocol versturen"; + T.config_mqtt_server = "Broker"; // zo heet dat in MQTT + T.config_mqtt_port = "Broker TCP-poort"; + T.config_max_failures = "Aantal verbindingsfouten voor automatische herstart"; + T.config_mqtt_topic = "Topic"; // zo heet dat in MQTT + T.config_mqtt_interval = "Publicatie-interval [s]"; + T.config_mqtt_template = "Berichtsjabloon"; + T.config_template_info = "De {} in het sjabloon wordt vervangen door de gemeten waarde."; + T.connecting = "Verbinden met WiFi..."; + T.portal_instructions = { + { + "Voor configuratie,", + "verbind met WiFi", + "\"{ssid}\"", + "met een smartphone." + }, + { + "Volg instructies op", + "uw smartphone.", + "(inlog-notificatie)" + }, + { + "Wijzig instellingen", + "en klik op \"Opslaan\".", + "(rechtsonder)" + }, + { + "Wijzig instellingen", + "en klik op \"Opslaan\".", + "Of \"Herstarten\"", + "als u klaar bent." + } + }; + return true; + } + + return false; +} + +} // namespace diff --git a/platformio.ini b/platformio.ini index 927db8a..ba61e1f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,13 +21,18 @@ framework = arduino targets = upload monitor_speed = 115200 lib_deps = - ESP-WiFiSettings@^3.7 + ESP-WiFiSettings@^3.7.2 MH-Z19 TFT_eSPI MQTT build_flags = +# ESP-WiFiSettings languages: + -DLANGUAGE_EN + -DLANGUAGE_NL +# ESP32 debugging: # -DCORE_DEBUG_LEVEL=5 +# TFT_eSPI configuration: -DUSER_SETUP_LOADED=1 -DST7789_DRIVER=1 -DCGRAM_OFFSET=1 From 29d873d268e3fb8e888f4c5ff32ee061ecdeedff Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Tue, 26 Jan 2021 07:16:24 +0100 Subject: [PATCH 07/10] Show message during SPIFFS format Also moved startup order around so module error message can be localized with user language. --- operame.ino | 38 +++++++++++++++++++++++--------------- operame_strings.h | 15 +++++++++++++++ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/operame.ino b/operame.ino index a8f2fcc..d9c564a 100644 --- a/operame.ino +++ b/operame.ino @@ -267,25 +267,41 @@ int get_co2() { void setup() { Serial.begin(115200); Serial.println("Operame start"); - SPIFFS.begin(true); + + digitalWrite(pin_backlight, HIGH); + display.init(); + display.fillScreen(TFT_BLACK); + display.setRotation(1); + sprite.createSprite(display.width(), display.height()); + + OperameLanguage::select(T, LANGUAGE); + + if (!SPIFFS.begin(false)) { + display_lines(T.first_run, TFT_MAGENTA); + if (!SPIFFS.format()) { + display_big(T.error_format, TFT_RED); + delay(20*1000); + } + } pinMode(pin_portalbutton, INPUT_PULLUP); pinMode(pin_demobutton, INPUT_PULLUP); pinMode(pin_pcb_ok, INPUT_PULLUP); pinMode(pin_backlight, OUTPUT); - digitalWrite(pin_backlight, HIGH); - - display.init(); - display.fillScreen(TFT_BLACK); - display.setRotation(1); - sprite.createSprite(display.width(), display.height()); + WiFiSettings.hostname = "operame-"; + WiFiSettings.language = LANGUAGE; + WiFiSettings.begin(); + OperameLanguage::select(T, WiFiSettings.language); while (digitalRead(pin_pcb_ok)) { display_big(T.error_module, TFT_RED); delay(1000); } + display_logo(); + delay(2000); + hwserial1.begin(9600, SERIAL_8N1, pin_sensor_rx, pin_sensor_tx); if (aqc_get_co2() >= 0) { @@ -298,14 +314,7 @@ void setup() { Serial.println("Using MHZ driver."); } - display_logo(); - delay(2000); - WiFiSettings.hostname = "operame-"; - WiFiSettings.language = LANGUAGE; - WiFiSettings.begin(); - - OperameLanguage::select(T, WiFiSettings.language); for (auto& str : T.portal_instructions[0]) { str.replace("{ssid}", WiFiSettings.hostname); } @@ -328,7 +337,6 @@ void setup() { mqtt_template = WiFiSettings.string("operame_mqtt_template", "{} PPM", T.config_mqtt_template); WiFiSettings.info(T.config_template_info); - WiFiSettings.onConnect = [] { display_big(T.connecting, TFT_BLUE); check_portalbutton(); diff --git a/operame_strings.h b/operame_strings.h index eb4350c..cdae1d9 100644 --- a/operame_strings.h +++ b/operame_strings.h @@ -12,6 +12,7 @@ struct Texts { *error_timeout, *error_sensor, *error_wifi, + *error_format, *config_wifi, *config_ota, *config_co2_warning, @@ -29,6 +30,7 @@ struct Texts { *wait ; std::vector> portal_instructions; + std::list first_run; }; std::map languages { @@ -54,6 +56,7 @@ bool select(Texts& T, String language) { T.error_timeout = "Time's up"; T.error_sensor = "sensor error"; T.error_wifi = "WiFi failed!"; + T.error_format = "Formatting failed"; T.wait = "wait..."; T.config_wifi = "Use WiFi connection"; T.config_ota = "Enable wireless reprogramming. (Uses portal password!)"; @@ -93,6 +96,11 @@ bool select(Texts& T, String language) { "when you're done." } }; + T.first_run = { + "DO NOT TURN OFF", + "Initializing", + "flash memory.", + }; return true; } @@ -103,6 +111,7 @@ bool select(Texts& T, String language) { T.error_timeout = "Tijd verstreken"; T.error_sensor = "sensorfout"; T.error_wifi = "WiFi mislukt!"; + T.error_format = "Formatteren mislukt"; T.wait = "wacht..."; T.config_wifi = "WiFi-verbinding gebruiken"; T.config_ota = "Draadloos herprogrammeren inschakelen. (Gebruikt portaalwachtwoord!)"; @@ -142,6 +151,12 @@ bool select(Texts& T, String language) { "als u klaar bent." } }; + T.first_run = { + "NIET", + "UITSCHAKELEN", + "Flashgeheugen", + "wordt voorbereid." + }; return true; } From 8fa0ee91e7c83e39107e2a45fe006ad5c21a4bda Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Wed, 17 Feb 2021 05:01:08 +0100 Subject: [PATCH 08/10] Hacky parallel firmware flasher --- multi-upload.pl | 87 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 multi-upload.pl diff --git a/multi-upload.pl b/multi-upload.pl new file mode 100644 index 0000000..a300eba --- /dev/null +++ b/multi-upload.pl @@ -0,0 +1,87 @@ +#!/usr/bin/perl +use strict; +use warnings; +use autodie; +use Time::HiRes qw(sleep); +use POSIX qw(strftime); + +if (!@ARGV or $ARGV[0] ne "--please-destroy-my-data") { + print "DANGER! Please read and understand the source code before using.\n"; + print "Regular users do not need to use this program. Use 'pio run' instead.\n"; + exit 1; +} + +my %pane; +my $command = ""; + +sub printlog { + print strftime("%Y-%m-%d %H:%M:%S", localtime), " @_\n"; +} + +sub spawn { + my ($dev) = @_; + + printlog "Spawning $dev"; + + my $c = $command; + + $c =~ s[ttyUSB\w+][$dev]; + $c =~ s/^/time /; + $c =~ s[$][| perl -pe"BEGIN { \$/ = \\1 } s/\\r/\\n/"; echo "-- $dev done --"; read a]; + + $pane{$dev} = readpipe qq{tmux split-window -d -h -P -F '#{pane_id}' '$c'}; + chomp $pane{$dev}; + + system "tmux select-layout even-horizontal"; +} + +if (1 < (() = glob("/dev/ttyUSB*"))) { + die "Disconnect USB serial devices except one target device.\n"; +} + +if ($ARGV[-1] ne "it's-a-me") { + exec qw[tmux new perl], $0, @ARGV, "it's-a-me"; +} + +my $pio_pid = open my $pio, "-|", "pio run -v -t upload"; +while (defined(my $line = readline $pio)) { + print $line; + if ($line =~ /esptool/ && $line =~ /write_flash/) { + $command = $line; + system "pkill -9 -P $pio_pid"; # kill upload process. + kill 9, $pio_pid; + last; + } +} + +if ($command =~ /(ttyUSB\d+)/) { + spawn $1; +} else { + warn "Could not snatch upload command.\nMake sure a single target device is already connected.\nPress enter to exit.\n"; + scalar ; + die; +} + +open my $monitor, "-|", "udevadm monitor --kernel --subsystem-match=usb-serial"; + +printlog "Started monitoring for new usb-serial devices."; + + +while (defined(my $line = readline $monitor)) { + my ($event) = $line =~ /\b(remove|add)\b/ or next; + my ($dev) = $line =~ /(ttyUSB\d+)/ or next; + + if ($event eq 'add') { + while (!-w "/dev/$dev") { + sleep .1; + # wait for permissions to settle; + } + + spawn($dev); + } else { + if (exists $pane{$dev}) { + printlog "Killing $dev"; + system "tmux kill-pane -t $pane{$dev}"; + } + } +} From 2807ee2647a61522df8d06faa7c4188b98de6880 Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Thu, 11 Mar 2021 03:54:36 +0100 Subject: [PATCH 09/10] Implement manual calibration (command 0x87) --- operame.ino | 62 ++++++++++++++++++++++++++++++++++++++++++++--- operame_strings.h | 25 +++++++++++++++++++ 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/operame.ino b/operame.ino index d9c564a..975df83 100644 --- a/operame.ino +++ b/operame.ino @@ -113,11 +113,31 @@ void display_ppm(int ppm) { display_big(String(ppm), fg, bg); } +void calibrate() { + auto lines = T.calibration; + for (int count = 60; count >= 0; count--) { + lines.back() = String(count); + display_lines(lines, TFT_RED); + unsigned long start = millis(); + while (millis() - start < 1000) { + if (button(pin_demobutton) || button(pin_portalbutton)) return; + } + } + + lines = T.calibrating; + if (driver == AQC) for (auto& line : lines) line.replace("400", "425"); + display_lines(lines, TFT_MAGENTA); + + set_zero(); // actually instantaneous + delay(15000); // give time to read long message +} + void ppm_demo() { display_big("demo!"); delay(3000); display_logo(); delay(1000); + int buttoncounter = 0; for (int p = 400; p < 1200; p++) { display_ppm(p); if (button(pin_demobutton)) { @@ -125,6 +145,18 @@ void ppm_demo() { delay(500); return; } + + // Hold portal button from 700 to 800 for manual calibration + if (p >= 700 && p < 800 && !digitalRead(pin_portalbutton)) { + buttoncounter++; + } + if (p == 800 && buttoncounter >= 85) { + while (!digitalRead(pin_portalbutton)) delay(100); + calibrate(); + display_logo(); + delay(500); + return; + } delay(30); } display_logo(); @@ -184,6 +216,13 @@ void connect_mqtt() { } } +void flush(Stream& s, int limit = 20) { + // .available() sometimes stays true (why?), hence the limit + + s.flush(); // flush output + while(s.available() && --limit) s.read(); // flush input +} + int aqc_get_co2() { static bool initialized = false; @@ -192,12 +231,10 @@ int aqc_get_co2() { int co2 = -1; for (int attempt = 0; attempt < 3; attempt++) { - hwserial1.flush(); - int limit = 20; // .available() sometimes stays true - while(hwserial1.available() && --limit) hwserial1.read(); - + flush(hwserial1); hwserial1.write(command, sizeof(command)); delay(50); + size_t c = hwserial1.readBytes(response, sizeof(response)); if (c != sizeof(response) || response[0] != 0xff || response[1] != 0x86) { continue; @@ -223,6 +260,11 @@ int aqc_get_co2() { return co2; } +void aqc_set_zero() { + const uint8_t command[9] = { 0xff, 0x01, 0x87, 0, 0, 0, 0, 0, 0x78 }; + hwserial1.write(command, sizeof(command)); +} + void mhz_setup() { mhz.begin(hwserial1); // mhz.setFilter(true, true); Library filter doesn't handle 0436 @@ -253,6 +295,10 @@ int mhz_get_co2() { return co2; } +void mhz_set_zero() { + mhz.calibrate(); +} + int get_co2() { // <0 means read error, 0 means still initializing, >0 is PPM value @@ -264,6 +310,14 @@ int get_co2() { return -1; // suppress warning } +void set_zero() { + if (driver == AQC) { aqc_set_zero(); return; } + if (driver == MHZ) { mhz_set_zero(); return; } + + // Should be unreachable + panic(T.error_driver); +} + void setup() { Serial.begin(115200); Serial.println("Operame start"); diff --git a/operame_strings.h b/operame_strings.h index cdae1d9..1370aea 100644 --- a/operame_strings.h +++ b/operame_strings.h @@ -31,6 +31,8 @@ struct Texts { ; std::vector> portal_instructions; std::list first_run; + std::list calibration; + std::list calibrating; }; std::map languages { @@ -101,6 +103,17 @@ bool select(Texts& T, String language) { "Initializing", "flash memory.", }; + T.calibration = { + "Manual calibration!", + "Press button", + "to cancel.", + "" + }; + T.calibrating = { + "Assuming current", + "CO2 level to be", + "400 PPM." + }; return true; } @@ -157,6 +170,18 @@ bool select(Texts& T, String language) { "Flashgeheugen", "wordt voorbereid." }; + T.calibration = { + "Handmatige", + "calibratie!", + "knop = stop", + "" + }; + T.calibrating = { + "Het huidige CO2-", + "niveau wordt", + "aangenomen", + "400 PPM te zijn." + }; return true; } From b84a6d8b19d262bff7b9f45bde518b29c04d7c8c Mon Sep 17 00:00:00 2001 From: Juerd Waalboer Date: Sat, 13 Mar 2021 19:04:19 +0100 Subject: [PATCH 10/10] Flush serial buffers before sending 0x87 --- operame.ino | 1 + 1 file changed, 1 insertion(+) diff --git a/operame.ino b/operame.ino index 975df83..d755952 100644 --- a/operame.ino +++ b/operame.ino @@ -262,6 +262,7 @@ int aqc_get_co2() { void aqc_set_zero() { const uint8_t command[9] = { 0xff, 0x01, 0x87, 0, 0, 0, 0, 0, 0x78 }; + flush(hwserial1); hwserial1.write(command, sizeof(command)); }