-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathanaire.PiCO2.ino
1923 lines (1650 loc) · 70.2 KB
/
anaire.PiCO2.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Anaire PiCO2 - Open CO2, temperature and humidity measurement device connected to Cloud Application www.anaire.org
// 202105 anaire.org [email protected]
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// HW Parts:
// Control board: LILYGO® TTGO T-Display ESP32 WiFi And Bluetooth https://github.com/Xinyuan-LilyGO/TTGO-T-Display
// Sensirion SCD30 CO2 temp and humidity sensor https://www.sensirion.com/en/environmental-sensors/carbon-dioxide-sensors/carbon-dioxide-sensors-scd30/
// Buzzer: AZDelivery Active Buzzer - https://www.az-delivery.de/es/products/buzzer-modul-aktiv?_pos=2&_sid=39cea0af6&_ss=r
// 3D Box designed by Anaire: https://www.thingiverse.com/thing:4854504
// 3.7V lithium battery with JST Connector 2Pin 1.25mm, ex. https://www.amazon.es/dp/B087LTZW61/ref=cm_sw_r_cp_awdb_imm_BPVY2QA8X3P5GB9NQ55C
//
// SW Setup:
// Install the usb to uart driver from https://www.silabs.com/developers/usb-to-uart-bridge-vcp-drivers
// Setup the Arduino IDE for the ESP32 platform: https://github.com/espressif/arduino-esp32
// Start Arduino and open Preferences window
// Enter https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json into Additional Board Manager URLs field. You can add multiple URLs, separating them with commas.
// Open Boards Manager from Tools-> Board menu and find esp32 platform and install the latest version.
// Select "ESP32 Dev Module" board from Tools-> Board menu after installation
// Select Tools-> Partition Scheme-> Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS)
// Setup the required libraries:
// - TTGO T-Display's Git: Download the code as zip https://github.com/Xinyuan-LilyGO/TTGO-T-Display/archive/master.zip
// Extract it and copy the Folder TFT_eSPI to your Arduino library path (usually <your user>/Documents/Arduino in Windows)
// - Install the following libraries from Arduino IDE, select Tools -> Library manager.
// - Search for Adafruit SCD30 by Adafruit https://github.com/adafruit/Adafruit_SCD30 and install the library
// - Search for WifiManager by tzapu,tablatronix https://github.com/tzapu/WiFiManager and install the library
// - Search for PubSubClient by Nick O'Leary https://github.com/knolleary/pubsubclient and install the library
// - Search for ArduinoJson by Benoît Blanchon https://github.com/bblanchon/ArduinoJson and install the library
// - Search for Button2 by Lennart Hennings https://github.com/LennartHennigs/Button2 and install the library
// - If you plan to activate bluetooth measurements, install the Sensirion Gadget BLE Arduino Library https://github.com/Sensirion/Sensirion_GadgetBle_Arduino_Library/releases
// Download latest zip. In the Arduino IDE, select Sketch -> include Library -> Add .zip Library and select the downloaded .zip file
//
// Buttons design:
// - Top button short click: info about the device
// - Top button LONG click: toggles acoustic alarm; enabled by default
// - Top button double click: sleep; click a button to wake up
// - Top button triple click: starts captive portal
// - Bottom button short click: buttons usage
// - Bottom button LONG click: performs CO2 sensor forced calibration
// - Bottom button double click: restart device
// - Bottom button triple click: enables auto self calibration
//
// Most functions are available through buttons, the web server and the Anaire Cloud Application
// - WiFi configuration is only available through the captive portal. Portal can be launched from bottom button double click, or from the web server
// - MQTT endpoint only available through captive portal and Anaire Cloud Application (after being initially connected)
// - Forced calibration reference value change only available through Anaire Cloud Application
// - Remote firmware upgrade only available through Anaire Cloud Application
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
String sw_version = "v3.20210520.Malik";
// v3.20210520.Malik - Changed voltage thresholds and binary remote upgrade, after changing github documentation and folders
// v3.20210515.Kamasi - Changes in buttons functionality; web server completed; bluetooth enabled/disabled by a precompiler directive
// v3.20210512.Canine - Minor fixes: WiFi error recovery corrected; restart and captive portal added to web server
// v3.20210510.Berenice - Minor fixes: alarm on/off message after pressing top button
// v3.20210509.Anita - Minor fixes (IES Ppe Felipe)
// v3.20210508.Lenora - CO2 measurements each 10s; MQTT message each 60s; Temperature offset 600 (celsius hundredths) and altitude compensation to 600m by default
// v3.20210506.EllaFitz - Bug fixes
// v3.20210506.AEOC - CO2 measurements each 30s, MQTT sending each 60s. SCD30 is not reset anymore after a reading failure
// v3.20210506.Bona - Added battery voltage measurement in the MQTT message
// v3.20210504.Alain - OTA updates
// v3.20210504.Parker - Icons for battery, wifi and alarm
// v3.20210503.Mingus - Lots of improvements, first fully functional version; MQTT commandes not yet tested
// v3.20210502.Madrid - Lots of additions: SCD30 full support, coherent display messages, complete buttons support, etc.
// v3.20210430.Kuti - Bluetooth commented out for later to get captive portal fully functional
// v3.20210425.Samba - First fully functional Anaire PiCO2 device on TTGo T-Display board, connected to Anaire Cloud App
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define BLUETOOTH false // Set to true in case bluetooth is desired
// device id, automatically filled by concatenating the last three fields of the wifi mac address, removing the ":" in betweeen, in HEX format. Example: ChipId (HEX) = 85e646, ChipId (DEC) = 8775238, macaddress = E0:98:06:85:E6:46
String anaire_device_id;
// Init to default values; if they have been chaged they will be readed later, on initialization
struct MyConfigStruct {
char anaire_device_name[24]; // Device name; default to anaire_device_id
uint16_t CO2ppm_warning_threshold = 700; // Warning threshold; default to 700ppm
uint16_t CO2ppm_alarm_threshold = 1000; // Alarm threshold; default to 1000ppm
char MQTT_server[24] = "mqtt.anaire.org"; // MQTT server url or public IP address. Default to Anaire Portal on portal.anaire.org
uint16_t MQTT_port = 80; // MQTT port; Default to Anaire Port on 30183
boolean acoustic_alarm = true; // Global flag to control acoustic alarm; default to true
boolean self_calibration = false; // Automatic Baseline Correction of CO2 sensor; default to false
uint16_t forced_recalibration_reference = 420; // Forced Recalibration value; default to 420ppm
uint16_t temperature_offset = 600; // temperature offset for SCD30 CO2 measurements: 600 by default, because of the housing
uint16_t altitude_compensation = 600; // altitude compensation for SCD30 CO2 measurements: 600, Madrid altitude
char wifi_user[24]; // WiFi user to be used on WPA Enterprise. Default to null (not used)
char wifi_password[24]; // WiFi password to be used on WPA Enterprise. Default to null (not used)
} eepromConfig;
// to store data on nvs partition
#include <Preferences.h>
Preferences preferences;
// Measurements
int CO2ppm_value = 0; // CO2 ppm measured value
int CO2ppm_accumulated = 0; // Accumulates co2 measurements for a MQTT period
int CO2ppm_samples = 0; // Counts de number of samples for a MQTT period
float temperature; // Read temperature as Celsius
float humidity; // Read humidity in %
// CO2 sensors
enum CO2_sensors {none, scd30_sensor}; // possible sensors integrated in the SW
CO2_sensors co2_sensor = none;
// CO2 device status
enum co2_status {co2_ok, co2_warning, co2_alarm}; // the device can have one of those CO2 status
co2_status co2_device_status = co2_ok; // initialized to ok
// device status
boolean err_global = false;
boolean err_wifi = false;
boolean err_MQTT = false;
boolean err_sensor = false;
// Measurements loop: time between measurements
unsigned int measurements_loop_duration = 10000; // 10 seconds
unsigned long measurements_loop_start; // holds a timestamp for each control loop start
// MQTT loop: time between MQTT measurements sent to the cloud
unsigned int MQTT_loop_duration = 60000; // 60 seconds
unsigned long MQTT_loop_start; // holds a timestamp for each cloud loop start
unsigned long lastReconnectAttempt = 0; // MQTT reconnections
// Errors loop: time between error condition recovery
unsigned int errors_loop_duration = 60000; // 60 seconds
unsigned long errors_loop_start; // holds a timestamp for each error loop start
// TTGO ESP32 board
#include "esp_timer.h"
#include <Wire.h>
// Display and fonts
#include <TFT_eSPI.h>
#include <SPI.h>
//#include "SensirionSimple25pt7b.h"
#include "ArchivoNarrow_Regular10pt7b.h"
#include "ArchivoNarrow_Regular50pt7b.h"
#define GFXFF 1
//#define FF99 &SensirionSimple25pt7b
#define FF90 &ArchivoNarrow_Regular10pt7b
#define FF95 &ArchivoNarrow_Regular50pt7b
TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke library, pins defined in User_Setup.h
// Customized Anaire splash screen
#include "anaire_ttgo_splash.h"
// Buttons: Top and bottom considered when USB connector is positioned on the right of the board
#include "Button2.h"
#define BUTTON_TOP 35
#define BUTTON_BOTTOM 0
Button2 button_top(BUTTON_TOP);
Button2 button_bottom(BUTTON_BOTTOM);
// Define ADC PIN for battery voltage measurement
#define ADC_PIN 34
float battery_voltage;
int vref = 1100;
// Define voltage threshold
#define USB_Voltage 4.0
#define Voltage_Threshold_1 3.9
#define Voltage_Threshold_2 3.7
#define Voltage_Threshold_3 3.5
#define Voltage_Threshold_4 3.3
// Sensirion SCD30 CO2, temperature and humidity sensor
#include <Adafruit_SCD30.h>
Adafruit_SCD30 scd30;
#define SCD30_SDA_pin 26 // Define the SDA pin used for the SCD30
#define SCD30_SCL_pin 27 // Define the SCL pin used for the SCD30
unsigned long SCD30_CALIBRATION_TIME = 60000; // SCD30 CO2 CALIBRATION TIME: 1 min = 60000 ms
// Bluetooth in TTGO T-Display
#if BLUETOOTH
#include "Sensirion_GadgetBle_Lib.h" // to connect to Sensirion MyAmbience Android App available on Google Play
GadgetBle gadgetBle = GadgetBle(GadgetBle::DataType::T_RH_CO2_ALT);
bool bluetooth_active = false;
#endif
// AZ-Delivery Active Buzzer
#define BUZZER_GPIO 12 // signal GPIO12 (pin TOUCH5/ADC15/GPIO12 on TTGO)
// WiFi
//#include <WiFi.h>
#include <WiFiManager.h> // https://github.com/tzapu/WiFiManager
#include "esp_wpa2.h" //wpa2 library for connections to Enterprise networks
const int WIFI_CONNECT_TIMEOUT = 10000; // 10 seconds
WiFiServer wifi_server(80);
WiFiClient wifi_client;
// MQTT
#include <PubSubClient.h>
char MQTT_message[256];
PubSubClient MQTT_client(wifi_client);
char received_payload[384];
String MQTT_send_topic;
String MQTT_receive_topic;
//JSON
#include <ArduinoJson.h>
StaticJsonDocument<384> jsonBuffer;
// OTA Update
#include <HTTPClient.h>
#include <HTTPUpdate.h>
// to know when there is an updating process in place
bool updating = false;
// To know when the device is in the following states
bool InCaptivePortal = false;
bool Calibrating = false;
///////////////////////////////////////////////////////////////////////////////////////////////////
// SETUP
///////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
// Initialize serial port for serial monitor in Arduino IDE
Serial.begin(115200);
while (!Serial) {
delay(500); // wait 0.5 seconds for connection
}
Serial.setDebugOutput(true);
// print info
Serial.println();
Serial.println("### INIT ANAIRE PiCO2 DEVICE ###########################################");
// Initialize TTGO Display and show Anaire splash screen
Display_Init();
Display_Splash_Screen();
// init preferences to handle persitent config data
preferences.begin("config"); // use "config" namespace
// Get device id
Get_Anaire_DeviceId();
// Set MQTT topics
MQTT_send_topic = "measurement"; // Measurements are sent to this topic
MQTT_receive_topic = "config/" + anaire_device_id; // Config messages will be received in config/id
// Read EEPROM config values
//Wipe_EEPROM();
Read_EEPROM();
Print_Config();
// Initialize the GadgetBle Library for Bluetooth
#if BLUETOOTH
gadgetBle.begin();
Serial.print("Sensirion GadgetBle Lib initialized with deviceId = ");
Serial.println(gadgetBle.getDeviceIdString());
#endif
// Initialize buzzer to OFF
pinMode(BUZZER_GPIO, OUTPUT);
digitalWrite(BUZZER_GPIO, LOW);
// Initialize TTGO board buttons
Button_Init();
// Attempt to connect to WiFi network:
Connect_WiFi();
// Attempt to connect to MQTT broker
if (!err_wifi) {
Init_MQTT();
}
// Initialize and warm up CO2 sensor
Setup_Sensor();
// Init control loops
measurements_loop_start = millis();
MQTT_loop_start = millis();
errors_loop_start = millis();
Serial.println("### ANAIRE PiCO2 DEVICE SETUP FINISHED ###\n");
tft.fillScreen(TFT_BLUE);
tft.setTextColor(TFT_WHITE, TFT_BLUE);
tft.setTextDatum(6); // bottom left
tft.setTextSize(1);
tft.setFreeFont(FF90);
tft.setTextDatum(MC_DATUM);
tft.drawString("ANAIRE PiCO2", tft.width()/2, tft.height()/2);
delay(1000);
// Update display with new values
Update_Display();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// CONTROL LOOP
///////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
//Serial.println ("--- LOOP BEGIN ---");
// If a firmware update is in progress do not do anything else
if (updating) {
return;
}
// Measure the battery voltage
//battery_voltage = ((float)analogRead(ADC_PIN)/4095.0)*2.0*3.3*(vref/1000.0);
// Measurement loop
if ((millis() - measurements_loop_start) >= measurements_loop_duration)
{
// New timestamp for the loop start time
measurements_loop_start = millis();
// Read sensors
Read_Sensor();
if (CO2ppm_value > 0) {
// Evaluate CO2 value
Evaluate_CO2_Value();
// Update display with new values
Update_Display();
// Update bluetooth app with new values
#if BLUETOOTH
Write_Bluetooth();
#endif
// Accumulates samples
CO2ppm_accumulated += CO2ppm_value;
CO2ppm_samples++;
}
}
// MQTT loop
if ((millis() - MQTT_loop_start) >= MQTT_loop_duration)
{
// New timestamp for the loop start time
MQTT_loop_start = millis();
// Message the MQTT broker in the cloud app to send the measured values
if ((!err_wifi) && (CO2ppm_samples > 0)) {
Send_Message_Cloud_App_MQTT();
}
// Reset samples after sending them to the MQTT server
CO2ppm_accumulated = 0;
CO2ppm_samples = 0;
}
// Errors loop
if ((millis() - errors_loop_start) >= errors_loop_duration)
{
// New timestamp for the loop start time
errors_loop_start = millis();
// Try to recover error conditions
if (err_sensor) {
Serial.println ("--- err_sensor");
//Setup_Sensor(); // Init co2 sensors
}
if (WiFi.status() != WL_CONNECTED) {
Serial.println ("--- err_wifi");
err_wifi = true;
WiFi.reconnect();
}
else {
err_wifi = false;
}
//Reconnect MQTT if needed
if ((!MQTT_client.connected()) && (!err_wifi)) {
Serial.println ("--- err_mqtt");
err_MQTT = true;
}
//Reconnect MQTT if needed
if ((err_MQTT) && (!err_wifi)) {
Serial.println ("--- MQTT reconnect");
// Attempt to connect to MQTT broker
MQTT_Reconnect();
Init_MQTT();
}
}
// From here, all other tasks performed outside of measurements, MQTT and error loops
// if not there are not connectivity errors, receive MQTT messages
if ((!err_MQTT) && (!err_wifi)) {
MQTT_client.loop();
}
// Process wifi server requests
Check_WiFi_Server();
// Process bluetooth events
#if BLUETOOTH
gadgetBle.handleEvents();
#endif
// Process buttons events
button_top.loop();
button_bottom.loop();
//Serial.println("--- END LOOP");
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// FUNCTIONS
///////////////////////////////////////////////////////////////////////////////////////////////////
void Connect_WiFi() { // Connect to WiFi
WiFi.disconnect(true); //disconnect form wifi to set new wifi connection
WiFi.mode(WIFI_STA); //init wifi mode
WiFi.onEvent(WiFiEvent);
wifi_config_t conf;
esp_wifi_get_config(WIFI_IF_STA, &conf); // Get WiFi configuration
Serial.print("Attempting to connect to WiFi network: ");
Serial.println(String(reinterpret_cast<const char*>(conf.sta.ssid))); // WiFi.SSID() is not filled up until the connection is established
// If there are not wifi user and wifi password defined, proceed to traight forward configuration
if ((strlen(eepromConfig.wifi_user) == 0) && (strlen(eepromConfig.wifi_password) == 0)) {
Serial.println("Attempting to authenticate...");
}
else { // set up wpa2 enterprise
Serial.println("Attempting to authenticate using WPA2 Enterprise...");
Serial.print("User: ");
Serial.println(eepromConfig.wifi_user);
Serial.print("Password: ");
Serial.println(eepromConfig.wifi_password);
esp_wifi_sta_wpa2_ent_set_identity((uint8_t *)eepromConfig.wifi_user, strlen(eepromConfig.wifi_user)); //provide identity
esp_wifi_sta_wpa2_ent_set_username((uint8_t *)eepromConfig.wifi_user, strlen(eepromConfig.wifi_user)); //provide username --> identity and username is same
esp_wifi_sta_wpa2_ent_set_password((uint8_t *)eepromConfig.wifi_password, strlen(eepromConfig.wifi_password)); //provide password
esp_wpa2_config_t config = WPA2_CONFIG_INIT_DEFAULT(); //set config settings to default
esp_wifi_sta_wpa2_ent_enable(&config); //set config settings to enable function
}
// Connect to wifi
WiFi.begin();
// Timestamp for connection timeout
int wifi_timeout_start = millis();
// Wait for warming time while blinking blue led
while ((WiFi.status() != WL_CONNECTED) && ((millis() - wifi_timeout_start) < WIFI_CONNECT_TIMEOUT)) {
delay(500); // wait 0.5 seconds for connection
Serial.println(".");
}
// Status
if (WiFi.status() != WL_CONNECTED) {
err_wifi = true;
Serial.println("WiFi not connected");
}
else {
err_wifi = false;
Serial.println("WiFi connected");
// start the web server on port 80
wifi_server.begin();
}
Print_WiFi_Status();
}
void Print_WiFi_Status() { // Print wifi status on serial monitor
// Get current status
// WL_CONNECTED: assigned when connected to a WiFi network;
// WL_NO_SHIELD: assigned when no WiFi shield is present;
// WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);
// WL_NO_SSID_AVAIL: assigned when no SSID are available;
// WL_SCAN_COMPLETED: assigned when the scan networks is completed;
// WL_CONNECT_FAILED: assigned when the connection fails for all the attempts;
// WL_CONNECTION_LOST: assigned when the connection is lost;
// WL_DISCONNECTED: assigned when disconnected from a network;
Serial.print("wifi_status: ");
switch (WiFi.status()) {
case WL_CONNECTED:
Serial.println("WiFi connected");
break;
case WL_NO_SHIELD:
Serial.println("No WiFi HW detected");
break;
case WL_IDLE_STATUS:
Serial.println("Attempting...");
break;
case WL_NO_SSID_AVAIL:
Serial.println("No SSID available");
break;
case WL_SCAN_COMPLETED:
Serial.println("Networks scan completed");
break;
case WL_CONNECT_FAILED:
Serial.println("Connect failed");
break;
case WL_CONNECTION_LOST:
Serial.println("Connection lost");
break;
case WL_DISCONNECTED:
Serial.println("Disconnected");
break;
default:
Serial.println("Unknown status");
break;
}
// Print the SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
// Print your WiFi shield's IP address:
Serial.print("IP Address: ");
Serial.println(WiFi.localIP());
// Print your WiFi shield's MAC address:
Serial.print("MAC Adress: ");
Serial.println(WiFi.macAddress());
// Print the received signal strength:
Serial.print("Signal strength (RSSI):");
Serial.print(WiFi.RSSI());
Serial.println(" dBm");
/*
// Print authentication used:
Serial.print("Encryption type: ");
switch (WiFi.encryptionType()) {
case WIFI_AUTH_OPEN:
Serial.println("Open WiFi");
break;
case WIFI_AUTH_WEP:
Serial.println("WEP");
break;
case WIFI_AUTH_WPA_PSK:
Serial.println("WPA-PSK");
break;
case WIFI_AUTH_WPA2_PSK:
Serial.println("WPA2-PSK");
break;
case WIFI_AUTH_WPA_WPA2_PSK:
Serial.println("WPA-WPA2-PSK");
break;
case WIFI_AUTH_WPA2_ENTERPRISE:
Serial.println("WPA2-Enterprise");
break;
default:
Serial.println("Unknown encryption type");
break;
}
*/
}
void WiFiEvent(WiFiEvent_t event) {
Serial.printf("[WiFi-event] event: %d - ", event);
switch (event) {
case SYSTEM_EVENT_WIFI_READY:
Serial.println("WiFi interface ready");
break;
case SYSTEM_EVENT_SCAN_DONE:
Serial.println("Completed scan for access points");
break;
case SYSTEM_EVENT_STA_START:
Serial.println("WiFi client started");
break;
case SYSTEM_EVENT_STA_STOP:
Serial.println("WiFi clients stopped");
break;
case SYSTEM_EVENT_STA_CONNECTED:
Serial.println("Connected to access point");
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("Disconnected from WiFi access point");
break;
case SYSTEM_EVENT_STA_AUTHMODE_CHANGE:
Serial.println("Authentication mode of access point has changed");
break;
case SYSTEM_EVENT_STA_GOT_IP:
Serial.print("Obtained IP address: ");
Serial.println(WiFi.localIP());
break;
case SYSTEM_EVENT_STA_LOST_IP:
Serial.println("Lost IP address and IP address is reset to 0");
break;
case SYSTEM_EVENT_STA_WPS_ER_SUCCESS:
Serial.println("WiFi Protected Setup (WPS): succeeded in enrollee mode");
break;
case SYSTEM_EVENT_STA_WPS_ER_FAILED:
Serial.println("WiFi Protected Setup (WPS): failed in enrollee mode");
break;
case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT:
Serial.println("WiFi Protected Setup (WPS): timeout in enrollee mode");
break;
case SYSTEM_EVENT_STA_WPS_ER_PIN:
Serial.println("WiFi Protected Setup (WPS): pin code in enrollee mode");
break;
case SYSTEM_EVENT_AP_START:
Serial.println("WiFi access point started");
break;
case SYSTEM_EVENT_AP_STOP:
Serial.println("WiFi access point stopped");
break;
case SYSTEM_EVENT_AP_STACONNECTED:
Serial.println("Client connected");
break;
case SYSTEM_EVENT_AP_STADISCONNECTED:
Serial.println("Client disconnected");
break;
case SYSTEM_EVENT_AP_STAIPASSIGNED:
Serial.println("Assigned IP address to client");
break;
case SYSTEM_EVENT_AP_PROBEREQRECVED:
Serial.println("Received probe request");
break;
case SYSTEM_EVENT_GOT_IP6:
Serial.println("IPv6 is preferred");
break;
case SYSTEM_EVENT_ETH_START:
Serial.println("Ethernet started");
break;
case SYSTEM_EVENT_ETH_STOP:
Serial.println("Ethernet stopped");
break;
case SYSTEM_EVENT_ETH_CONNECTED:
Serial.println("Ethernet connected");
break;
case SYSTEM_EVENT_ETH_DISCONNECTED:
Serial.println("Ethernet disconnected");
break;
case SYSTEM_EVENT_ETH_GOT_IP:
Serial.println("Obtained IP address");
break;
default: break;
}
}
void Check_WiFi_Server() { // Wifi server
WiFiClient client = wifi_server.available(); // listen for incoming clients
if (client) { // if you get a client,
Serial.println("new client"); // print a message out the serial port
String currentLine = ""; // make a String to hold incoming data from the client
while (client.connected()) { // loop while the client's connected
if (client.available()) { // if there's bytes to read from the client,
char c = client.read(); // read a byte, then
Serial.write(c); // print it out the serial monitor
if (c == '\n') { // if the byte is a newline character
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
// Print current info
client.print("ANAIRE PiCO2 DEVICE");
client.println("<br>");
client.print("SW version: ");
client.print(sw_version);
client.println("<br>");
client.println("------");
client.println("<br>");
client.print("Anaire Device ID: ");
client.print(anaire_device_id);
client.println("<br>");
client.print("Anaire Device name: ");
client.print(eepromConfig.anaire_device_name);
client.println("<br>");
client.print("SSID: ");
client.print(String(WiFi.SSID()));
client.println("<br>");
client.print("IP Adress: ");
client.print(WiFi.localIP());
client.println("<br>");
client.print("MAC Adress: ");
client.print(WiFi.macAddress());
client.println("<br>");
client.print("RSSI: ");
client.print(WiFi.RSSI());
client.println("<br>");
client.println("------");
client.println("<br>");
client.print("CO2ppm_warning_threshold: ");
client.print(eepromConfig.CO2ppm_warning_threshold);
client.println("<br>");
client.print("CO2ppm_alarm_threshold: ");
client.print(eepromConfig.CO2ppm_alarm_threshold);
client.println("<br>");
client.print("MQTT Server: ");
client.print(eepromConfig.MQTT_server);
client.println("<br>");
client.print("MQTT Port: ");
client.print(eepromConfig.MQTT_port);
client.println("<br>");
client.print("Alarm: ");
client.print(eepromConfig.acoustic_alarm);
client.println("<br>");
client.println("------");
client.println("<br>");
if (co2_sensor == scd30_sensor) {
client.print("CO2 Sensor: Sensirion SCD30");
client.println("<br>");
client.print("Measurement Interval: ");
client.print(measurements_loop_duration/1000);
client.println("<br>");
client.print("Auto Calibration: ");
client.print(eepromConfig.self_calibration);
client.println("<br>");
client.print("Forced Recalibration Reference: ");
client.print(eepromConfig.forced_recalibration_reference);
client.println("<br>");
client.print("Temperature Offset: ");
client.print(eepromConfig.temperature_offset);
client.println("<br>");
client.print("Altitude Compensation: ");
client.print(eepromConfig.altitude_compensation);
client.println("<br>");
}
client.println("------");
client.println("<br>");
client.print("CO2 PPM: ");
client.print(CO2ppm_value);
client.println("<br>");
client.print("Temperature: ");
client.print(temperature);
client.println("<br>");
client.print("Humidity: ");
client.print(humidity);
client.println("<br>");
client.print("CO2 STATUS: ");
switch (co2_device_status) {
case co2_ok:
client.print("OK");
break;
case co2_warning:
client.print("WARNING");
break;
case co2_alarm:
client.print("ALARM");
break;
}
client.println("<br>");
client.println("------");
client.println("<br>");
// Calibration:
client.print("Click <a href=\"/1\">here</a> to calibrate the device.<br>");
client.println("<br>");
// Activate autocalibration Calibration:
client.print("Click <a href=\"/2\">here</a> to activate auto calibration.<br>");
client.println("<br>");
// Captive portal:
client.print("Click <a href=\"/3\">here</a> to launch captive portal to set up WiFi and MQTT endpoint.<br>");
client.println("<br>");
// Suspend:
client.print("Click <a href=\"/4\">here</a> to suspend the device.<br>");
client.println("<br>");
// Restart:
client.print("Click <a href=\"/5\">here</a> to restart the device.<br>");
client.println("<br>");
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
}
else { // if you got a newline, then clear currentLine:
currentLine = "";
}
}
else if (c != '\r') { // if you got anything else but a carriage return character,
currentLine += c; // add it to the end of the currentLine
}
// Check to see if the client request was "GET /1" to calibrate the sensor:
if (currentLine.endsWith("GET /1")) {
Do_Calibrate_Sensor();
}
// Check to see if the client request was "GET /2" to activate autocalibration:
if (currentLine.endsWith("GET /2")) {
eepromConfig.self_calibration = true;
Set_AutoSelfCalibration();
Write_EEPROM();
}
// Check to see if the client request was "GET /3" to launch captive portal:
if (currentLine.endsWith("GET /3")) {
Start_Captive_Portal();
}
// Check to see if the client request was "GET /4" to suspend the device:
if (currentLine.endsWith("GET /4")) {
Suspend_Device();
}
// Check to see if the client request was "GET /5" to restart the device:
if (currentLine.endsWith("GET /5")) {
ESP.restart();
}
}
}
// close the connection:
client.stop();
Serial.println("client disconnected");
}
}
void Start_Captive_Portal() { // Run a captive portal to configure WiFi and MQTT
InCaptivePortal = true;
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_RED, TFT_WHITE);
tft.setTextSize(1);
tft.setFreeFont(FF90);
tft.setTextDatum(MC_DATUM);
String wifiAP = "AnaireWiFi_" + anaire_device_id;
tft.drawString(wifiAP, tft.width()/2, tft.height()/2);
wifi_server.stop();
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
wifiManager.setDebugOutput(true);
//wifiManager.setCountry("ES"); // it is not recognizing the country...
wifiManager.disconnect();
WiFi.mode(WIFI_AP); // explicitly set mode, esp defaults to STA+AP
// Captive portal parameters
WiFiManagerParameter custom_wifi_html("<p>Set WPA2 Enterprise</p>"); // only custom html
WiFiManagerParameter custom_wifi_user("User", "WPA2 Enterprise user", eepromConfig.wifi_user, 24);
WiFiManagerParameter custom_wifi_password("Password", "WPA2 Enterprise Password", eepromConfig.wifi_password, 24);
WiFiManagerParameter custom_mqtt_html("<p>Set MQTT server</p>"); // only custom html
WiFiManagerParameter custom_mqtt_server("Server", "MQTT server", eepromConfig.MQTT_server, 24);
char port[6]; itoa(eepromConfig.MQTT_port, port, 10);
WiFiManagerParameter custom_mqtt_port("Port", "MQTT port", port, 6);
//wifiManager.setSaveParamsCallback(saveParamCallback);
// Add parameters
wifiManager.addParameter(&custom_wifi_html);
wifiManager.addParameter(&custom_wifi_user);
wifiManager.addParameter(&custom_wifi_password);
wifiManager.addParameter(&custom_mqtt_html);
wifiManager.addParameter(&custom_mqtt_server);
wifiManager.addParameter(&custom_mqtt_port);
//sets timeout in seconds until configuration portal gets turned off.
//If not specified device will remain in configuration mode until
//switched off via webserver or device is restarted.
wifiManager.setConfigPortalTimeout(60);
//it starts an access point
//and goes into a blocking loop awaiting configuration
//wifiManager.resetSettings(); // reset previous configurations
bool res = wifiManager.startConfigPortal(wifiAP.c_str());
if (!res) {
Serial.println("Not able to start captive portal");
} else {
//if you get here you have connected to the WiFi
Serial.println("Captive portal operative");
}
// Save parameters to EEPROM only if any of them changed
bool write_eeprom = false;
if (eepromConfig.wifi_user != custom_wifi_user.getValue()) {
strncpy(eepromConfig.wifi_user, custom_wifi_user.getValue(), sizeof(eepromConfig.wifi_user));
eepromConfig.wifi_user[sizeof(eepromConfig.wifi_user) - 1] = '\0';
write_eeprom = true;
Serial.print("WiFi user: ");
Serial.println(eepromConfig.wifi_user);
}
if (eepromConfig.wifi_password != custom_wifi_password.getValue()) {
strncpy(eepromConfig.wifi_password, custom_wifi_password.getValue(), sizeof(eepromConfig.wifi_password));
eepromConfig.wifi_password[sizeof(eepromConfig.wifi_password) - 1] = '\0';
write_eeprom = true;
Serial.print("WiFi password: ");
Serial.println(eepromConfig.wifi_password);
}
if (eepromConfig.MQTT_server != custom_mqtt_server.getValue()) {
strncpy(eepromConfig.MQTT_server, custom_mqtt_server.getValue(), sizeof(eepromConfig.MQTT_server));
eepromConfig.MQTT_server[sizeof(eepromConfig.MQTT_server) - 1] = '\0';
write_eeprom = true;
Serial.print("MQTT server: ");
Serial.println(eepromConfig.MQTT_server);
}
if (eepromConfig.MQTT_port != atoi(custom_mqtt_port.getValue())) {
eepromConfig.MQTT_port = atoi(custom_mqtt_port.getValue());
write_eeprom = true;
Serial.print("MQTT port: ");
Serial.println(eepromConfig.MQTT_port);
}
if (write_eeprom) {
Write_EEPROM();
}
InCaptivePortal = false;
// Restart
ESP.restart();
}
void Init_MQTT() { // MQTT Init function
Serial.print("Attempting to connect to the MQTT broker ");
Serial.print(eepromConfig.MQTT_server);
Serial.print(":");
Serial.println(eepromConfig.MQTT_port);
// Attempt to connect to MQTT broker
MQTT_client.setBufferSize(512); // to receive messages up to 512 bytes length (default is 256)
MQTT_client.setServer(eepromConfig.MQTT_server, eepromConfig.MQTT_port);
MQTT_client.setCallback(Receive_Message_Cloud_App_MQTT);
MQTT_client.connect(anaire_device_id.c_str());
if (!MQTT_client.connected()) {
err_MQTT = true;
MQTT_Reconnect();
}
else {
err_MQTT = false;
lastReconnectAttempt = 0;
// Once connected resubscribe
MQTT_client.subscribe(MQTT_receive_topic.c_str());
Serial.print("MQTT connected - Receive topic: ");
Serial.println(MQTT_receive_topic);
}
}
void MQTT_Reconnect() { // MQTT reconnect function
//Try to reconnect only if it has been more than 5 sec since last attemp
unsigned long now = millis();
if (now - lastReconnectAttempt > 5000) {
lastReconnectAttempt = now;
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (MQTT_client.connect(anaire_device_id.c_str())) {
err_MQTT = false;
Serial.println("MQTT connected");
lastReconnectAttempt = 0;
// Once connected resubscribe
MQTT_client.subscribe(MQTT_receive_topic.c_str());
Serial.print("MQTT connected - Receive topic: ");
Serial.println(MQTT_receive_topic);
} else {
err_MQTT = true;
Serial.print("failed, rc=");
Serial.print(MQTT_client.state());
Serial.println(" try again in 5 seconds");
}
}
}
void Send_Message_Cloud_App_MQTT() { // Send measurements to the cloud application by MQTT
// Print info
Serial.print("Sending MQTT message to the send topic: ");
Serial.println(MQTT_send_topic);
sprintf(MQTT_message, "{id: %s,CO2: %d,humidity: %f,temperature: %f,VBat: %f}", anaire_device_id.c_str(), (int) (CO2ppm_accumulated / CO2ppm_samples), humidity, temperature, battery_voltage);
Serial.print(MQTT_message);
Serial.println();
// send message, the Print interface can be used to set the message contents
MQTT_client.publish(MQTT_send_topic.c_str(), MQTT_message);
}
void Receive_Message_Cloud_App_MQTT(char* topic, byte* payload, unsigned int length) { // callback function to receive configuration messages from the cloud application by MQTT
boolean write_eeprom = false; // to track if writing the eeprom is required
memcpy(received_payload, payload, length);
Serial.print("Message arrived: ");
Serial.println(received_payload);
// Deserialize the JSON document
DeserializationError error = deserializeJson(jsonBuffer, received_payload);
// Test if parsing succeeds.
if (error) {
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
return;