From 6896807c331adf5accd2edf09ebc8ca1049e952f Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Sat, 21 Nov 2020 20:38:17 +0100 Subject: [PATCH 001/104] BOSCH BSEC upgrade tp 1.4.80 --- include/bmesensor.h | 58 +++++++++++++++++---------------------------- platformio_orig.ini | 3 ++- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/include/bmesensor.h b/include/bmesensor.h index c0d17475..5724db84 100644 --- a/include/bmesensor.h +++ b/include/bmesensor.h @@ -21,43 +21,29 @@ extern Ticker bmecycler; extern bmeStatus_t bme_status; // Make struct for storing gps data globally available -// --- Bosch BSEC v1.4.7.4 library configuration --- -// 3,3V supply voltage; 3s max time between sensor_control calls; 4 days +// --- Bosch BSEC library configuration --- +// We use 3,3V supply voltage; 3s max time between sensor_control calls; 4 days // calibration. Change this const if not applicable for your application (see -// BME680 datasheet) -// Note: 3s max time not exceed BMECYCLE frequency set in paxcounter.conf! -const uint8_t bsec_config_iaq[454] = { - 4, 7, 4, 1, 61, 0, 0, 0, 0, 0, 0, 0, 174, 1, 0, - 0, 48, 0, 1, 0, 0, 192, 168, 71, 64, 49, 119, 76, 0, 0, - 225, 68, 137, 65, 0, 63, 205, 204, 204, 62, 0, 0, 64, 63, 205, - 204, 204, 62, 0, 0, 0, 0, 216, 85, 0, 100, 0, 0, 0, 0, - 0, 0, 0, 0, 28, 0, 2, 0, 0, 244, 1, 225, 0, 25, 0, - 0, 128, 64, 0, 0, 32, 65, 144, 1, 0, 0, 112, 65, 0, 0, - 0, 63, 16, 0, 3, 0, 10, 215, 163, 60, 10, 215, 35, 59, 10, - 215, 35, 59, 9, 0, 5, 0, 0, 0, 0, 0, 1, 88, 0, 9, - 0, 229, 208, 34, 62, 0, 0, 0, 0, 0, 0, 0, 0, 218, 27, - 156, 62, 225, 11, 67, 64, 0, 0, 160, 64, 0, 0, 0, 0, 0, - 0, 0, 0, 94, 75, 72, 189, 93, 254, 159, 64, 66, 62, 160, 191, - 0, 0, 0, 0, 0, 0, 0, 0, 33, 31, 180, 190, 138, 176, 97, - 64, 65, 241, 99, 190, 0, 0, 0, 0, 0, 0, 0, 0, 167, 121, - 71, 61, 165, 189, 41, 192, 184, 30, 189, 64, 12, 0, 10, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 229, 0, 254, 0, 2, 1, 5, 48, - 117, 100, 0, 44, 1, 112, 23, 151, 7, 132, 3, 197, 0, 92, 4, - 144, 1, 64, 1, 64, 1, 144, 1, 48, 117, 48, 117, 48, 117, 48, - 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, 48, 117, 100, 0, - 100, 0, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 100, 0, 48, - 117, 48, 117, 48, 117, 100, 0, 100, 0, 100, 0, 48, 117, 48, 117, - 100, 0, 100, 0, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, - 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, 44, 1, - 44, 1, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, - 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, 8, 7, - 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, - 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 112, 23, 255, 255, - 255, 255, 255, 255, 255, 255, 220, 5, 220, 5, 220, 5, 255, 255, 255, - 255, 255, 255, 220, 5, 220, 5, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255, 255, 255, 255, 44, 1, 0, 0, 0, 0, - 138, 80, 0, 0}; +// BME680 datasheet) Note: 3s max time must not exceed BMECYCLE frequency set in +// paxcounter.conf! + +const uint8_t bsec_config_iaq[] = { +#include "config/generic_33v_3s_4d/bsec_iaq.txt" +}; + +/* Configure the BSEC library with information about the sensor + 18v/33v = Voltage at Vdd. 1.8V or 3.3V + 3s/300s = BSEC operating mode, BSEC_SAMPLE_RATE_LP or BSEC_SAMPLE_RATE_ULP + 4d/28d = Operating age of the sensor in days + generic_18v_3s_4d + generic_18v_3s_28d + generic_18v_300s_4d + generic_18v_300s_28d + generic_33v_3s_4d + generic_33v_3s_28d + generic_33v_300s_4d + generic_33v_300s_28d +*/ // Helper functions declarations int bme_init(); diff --git a/platformio_orig.ini b/platformio_orig.ini index dbaa6046..79e3bf75 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -75,7 +75,8 @@ lib_deps_sensors = adafruit/Adafruit Unified Sensor @ ^1.1.4 adafruit/Adafruit BME280 Library @ ^2.1.1 adafruit/Adafruit BMP085 Library @ ^1.1.0 - boschsensortec/BSEC Software Library @ 1.5.1474 + ;boschsensortec/BSEC Software Library @ 1.6.1480 + https://github.com/BoschSensortec/BSEC-Arduino-library.git https://github.com/ricki-z/SDS011.git lib_deps_basic = bblanchon/ArduinoJson @ <6 From b239961186b14dc1348e5e56fa012a23607a0e41 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 26 Nov 2020 21:31:46 +0100 Subject: [PATCH 002/104] issue #675 --- src/macsniff.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 798de95f..e25e6cf8 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -134,7 +134,7 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { uint32_t *oui; // temporary buffer for vendor OUI oui = (uint32_t *)MacBuffer.mac; // if we find OUI on vendor filter list we don't analyze and return early - if (std::find(vendors.begin(), vendors.end(), __builtin_bswap32(*oui) >> 8) != + if (std::find(vendors.begin(), vendors.end(), __builtin_bswap32(*oui) >> 8) == vendors.end()) return 0; #endif From 49d7ab80fa6af9d4acad5636939749ab7902d221 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 26 Nov 2020 21:32:04 +0100 Subject: [PATCH 003/104] issue #674 (experimental) --- src/power.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index 5d3d770e..ee07aba9 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -47,12 +47,10 @@ void AXP192_powerevent_IRQ(void) { if (pmu.isBattTempHighIRQ()) ESP_LOGI(TAG, "Battery low temperature."); -// short press -> esp32 deep sleep mode, can be exited by pressing user button -#ifdef HAS_BUTTON + // short press -> esp32 deep sleep mode, can be exited by pressing user button if (pmu.isPEKShortPressIRQ() && (RTC_runmode == RUNMODE_NORMAL)) { enter_deepsleep(0, HAS_BUTTON); } -#endif // long press -> shutdown power, can be exited by another longpress if (pmu.isPEKLongtPressIRQ()) { @@ -85,10 +83,12 @@ void AXP192_power(pmu_power_t powerlevel) { pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // lora off break; - default: // all rails power on - pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON); // Lora on T-Beam V1.0 - pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON); // Gps on T-Beam V1.0 - pmu.setPowerOutPut(AXP192_DCDC1, AXP202_ON); // OLED on T-Beam v1.0 + default: // all rails power on + pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON); // Lora on T-Beam V1.0 + pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON); // Gps on T-Beam V1.0 + pmu.setPowerOutPut(AXP192_DCDC1, AXP202_ON); // OLED on T-Beam v1.0 + pmu.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // unused on T-Beam v1.0 + pmu.setPowerOutPut(AXP192_EXTEN, AXP202_OFF); // unused on T-Beam v1.0 pmu.setChgLEDMode(AXP20X_LED_LOW_LEVEL); break; } @@ -121,6 +121,8 @@ void AXP192_init(void) { // configure AXP192 pmu.setDCDC1Voltage(3300); // for external OLED display + pmu.setLDO2Voltage(3300); // LORA VDD 3v3 + pmu.setLDO3Voltage(3300); // GPS VDD 3v3 pmu.setTimeOutShutdown(false); // no automatic shutdown pmu.setTSmode(AXP_TS_PIN_MODE_DISABLE); // TS pin mode off to save power @@ -138,7 +140,8 @@ void AXP192_init(void) { attachInterrupt(digitalPinToInterrupt(PMU_INT), PMUIRQ, FALLING); pmu.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | - AXP202_CHARGING_FINISHED_IRQ, + AXP202_CHARGING_FINISHED_IRQ | AXP202_PEK_SHORTPRESS_IRQ | + AXP202_PEK_LONGPRESS_IRQ, 1); pmu.clearIRQ(); #endif // PMU_INT From 4973ac67853e8dbfefd8f48a23c05772790709c9 Mon Sep 17 00:00:00 2001 From: Antonio Vanegas Date: Mon, 30 Nov 2020 15:58:27 -0500 Subject: [PATCH 004/104] Added initial support for TTGO-Tdisplay board (#676) --- README.md | 1 + img/Paxcounter-ttgo-tdisplay.jpg | Bin 0 -> 298541 bytes platformio_orig.ini | 1 + src/display.cpp | 2 +- src/hal/ttgotdisplay.h | 55 +++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 img/Paxcounter-ttgo-tdisplay.jpg create mode 100644 src/hal/ttgotdisplay.h diff --git a/README.md b/README.md index c6bb8a57..0241a3c4 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-L - Pyom: WiPy - WeMos: LoLin32, LoLin32 Lite, WeMos D32, [Wemos32 Oled](https://www.instructables.com/id/ESP32-With-Integrated-OLED-WEMOSLolin-Getting-Star/) - Crowdsupply: [TinyPICO](https://www.crowdsupply.com/unexpected-maker/tinypico) +- TTGO: [T-Display](https://www.aliexpress.com/item/33048962331.html) - Generic ESP32 Depending on board hardware following features are supported: diff --git a/img/Paxcounter-ttgo-tdisplay.jpg b/img/Paxcounter-ttgo-tdisplay.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7e3cac4b6911ee946487408e85eab8f1089178be GIT binary patch literal 298541 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

6ry`umW1${FH0n6!vUXohQbqb=WYtE%C+jA^e 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 + +#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature + +#define HAS_DISPLAY 2 // TFT-LCD, support work in progess, not ready yet +#define MY_DISPLAY_FLIP 1 // use if display is rotated + +#define HAS_LED NOT_A_PIN // no on board LED (?) +#define HAS_BUTTON (35) // on board button A + +// power management settings +#define BAT_MEASURE_ADC ADC1_GPIO34_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_6 +#define BAT_VOLTAGE_DIVIDER 2.605f // voltage divider + +// Display Settings +#define MY_DISPLAY_WIDTH 135 +#define MY_DISPLAY_HEIGHT 240 +#define MY_DISPLAY_INVERT 1 + +// setting for TTGO T-display +#define USER_SETUP_LOADED 1 +#define ST7789_DRIVER 1 + +#define CGRAM_OFFSET + +#define TFT_MOSI GPIO_NUM_19 // SPI +#define TFT_SCLK GPIO_NUM_18 // SPI +#define TFT_CS GPIO_NUM_5 // Chip select control +#define TFT_DC GPIO_NUM_16 // Data Command control +#define TFT_RST GPIO_NUM_23 // Reset +#define TFT_BL GPIO_NUM_4 // LED back-light + +#define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red + +#define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH +#define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters +#define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters +#define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm +#define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-. +#define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-. +//#define LOAD_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT +#define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts +#define SMOOTH_FONT + +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 6000000 + +#endif From 58e9bb6ab7f84d38f07d44e510d31ce9d9108963 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 2 Dec 2020 17:44:49 +0100 Subject: [PATCH 005/104] Update platformio esp version 2.0.0 -> 2.1.0 --- platformio_orig.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio_orig.ini b/platformio_orig.ini index def51a91..f505c2ab 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -7,7 +7,7 @@ ; ---> SELECT THE TARGET PLATFORM HERE! <--- [board] -halfile = generic.h +;halfile = generic.h ;halfile = ebox.h ;halfile = eboxtube.h ;halfile = ecopower.h @@ -19,7 +19,7 @@ halfile = generic.h ;halfile = ttgov21new.h ;halfile = ttgofox.h ;halfile = ttgobeam.h -;halfile = ttgobeam10.h +halfile = ttgobeam10.h ;halfile = ttgotdisplay.h ;halfile = fipy.h ;halfile = lopy.h @@ -47,7 +47,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 2.0.4 +release_version = 2.0.43 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 @@ -55,7 +55,7 @@ extra_scripts = pre:build.py otakeyfile = ota.conf lorakeyfile = loraconf.h lmicconfigfile = lmic_config.h -platform_espressif32 = espressif32@2.0.0 +platform_espressif32 = espressif32@2.1.0 monitor_speed = 115200 upload_speed = 115200 ; set by build.py and taken from hal file display_library = ; set by build.py and taken from hal file From af003ef461c63a126ade2339bb0143cd51f81420 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 2 Dec 2020 17:50:39 +0100 Subject: [PATCH 006/104] platformio_orig.ini update --- platformio_orig.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platformio_orig.ini b/platformio_orig.ini index f505c2ab..395a09a9 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -1,5 +1,5 @@ ; PlatformIO Project Configuration File -; NOTE: PlatformIO v4 is needed! +; NOTE: PlatformIO v5 is needed! ; ; Please visit documentation for the other options and examples ; http://docs.platformio.org/page/projectconf.html @@ -65,7 +65,7 @@ lib_deps_display = bitbank2/OneBitDisplay @ 1.9.0 bitbank2/BitBang_I2C @ ^2.1.3 ricmoo/QRCode @ ^0.0.1 - bodmer/TFT_eSPI @ ^2.2.20 + bodmer/TFT_eSPI @ ^2.3.51 lib_deps_ledmatrix = seeed-studio/Ultrathin_LED_Matrix @ ^1.0.0 lib_deps_rgbled = From 6bf43cc921cd476f52455d1df3ac8d32b12a4a84 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 3 Dec 2020 18:24:12 +0100 Subject: [PATCH 007/104] update BOSCH BSEC version --- platformio_orig.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/platformio_orig.ini b/platformio_orig.ini index 395a09a9..7f63d4e7 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -76,8 +76,7 @@ lib_deps_sensors = adafruit/Adafruit Unified Sensor @ ^1.1.4 adafruit/Adafruit BME280 Library @ ^2.1.1 adafruit/Adafruit BMP085 Library @ ^1.1.0 - ;boschsensortec/BSEC Software Library @ 1.6.1480 - https://github.com/BoschSensortec/BSEC-Arduino-library.git + boschsensortec/BSEC Software Library @ 1.6.1480 https://github.com/ricki-z/SDS011.git lib_deps_basic = bblanchon/ArduinoJson @ <6 From 04a055b329270d89d96b6e234b7de3cd4cae1562 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Fri, 4 Dec 2020 17:32:10 +0100 Subject: [PATCH 008/104] sleep cycle mode (experimental) --- README.md | 5 +++++ include/globals.h | 1 + src/configmanager.cpp | 3 ++- src/rcommand.cpp | 15 +++++++++++---- src/reset.cpp | 17 +++++++++++------ src/senddata.cpp | 4 ++++ 6 files changed, 34 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 0241a3c4..a5f01596 100644 --- a/README.md +++ b/README.md @@ -515,6 +515,11 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0 = disabled [default] 1 = enabled +0x19 set sleep cycle + + 0 ... 255 device sleep cycle in seconds/2 + e.g. 120 -> device sleeps 240 seconds after each send cycle [default = 0] + 0x80 get device configuration Device answers with it's current configuration on Port 3. diff --git a/include/globals.h b/include/globals.h index 39a42f79..5438fa3b 100644 --- a/include/globals.h +++ b/include/globals.h @@ -84,6 +84,7 @@ typedef struct __attribute__((packed)) { uint8_t countermode; // 0=cyclic unconfirmed, 1=cumulative, 2=cyclic confirmed int16_t rssilimit; // threshold for rssilimiter, negative value! uint8_t sendcycle; // payload send cycle [seconds/2] + uint8_t sleepcycle; // sleep cycle [seconds/2] uint8_t wifichancycle; // wifi channel switch cycle [seconds/100] uint8_t blescantime; // BLE scan cycle duration [seconds] uint8_t blescan; // 0=disabled, 1=enabled diff --git a/src/configmanager.cpp b/src/configmanager.cpp index f947f383..a489801e 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -42,6 +42,7 @@ static void defaultConfig(configData_t *myconfig) { COUNTERMODE; // 0=cyclic, 1=cumulative, 2=cyclic confirmed myconfig->rssilimit = 0; // threshold for rssilimiter, negative value! myconfig->sendcycle = SENDCYCLE; // payload send cycle [seconds/2] + myconfig->sleepcycle = SLEEPCYCLE; // sleep cycle [seconds/2] myconfig->wifichancycle = WIFI_CHANNEL_SWITCH_INTERVAL; // wifi channel switch cycle [seconds/100] myconfig->blescantime = @@ -53,7 +54,7 @@ static void defaultConfig(configData_t *myconfig) { myconfig->vendorfilter = VENDORFILTER; // 0=disabled, 1=enabled myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) myconfig->monitormode = 0; // 0=disabled, 1=enabled - myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default + myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled #ifdef HAS_BME680 diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 75899eca..bd451d73 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -54,6 +54,12 @@ void set_sendcycle(uint8_t val[]) { cfg.sendcycle * 2); } +void set_sleepcycle(uint8_t val[]) { + cfg.sleepcycle = val[0]; + ESP_LOGI(TAG, "Remote command: set sleep cycle to %d seconds", + cfg.sleepcycle * 2); +} + void set_wifichancycle(uint8_t val[]) { cfg.wifichancycle = val[0]; // update Wifi channel rotation timer period @@ -365,10 +371,11 @@ static const cmd_t table[] = { {0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true}, {0x15, set_bme, 1, true}, {0x16, set_batt, 1, true}, {0x17, set_wifiscan, 1, true}, {0x18, set_enscount, 1, true}, - {0x80, get_config, 0, false}, {0x81, get_status, 0, false}, - {0x83, get_batt, 0, false}, {0x84, get_gps, 0, false}, - {0x85, get_bme, 0, false}, {0x86, get_time, 0, false}, - {0x87, set_time, 0, false}, {0x99, set_flush, 0, false}}; + {0x19, set_sleepcycle, 1, true}, {0x80, get_config, 0, false}, + {0x81, get_status, 0, false}, {0x83, get_batt, 0, false}, + {0x84, get_gps, 0, false}, {0x85, get_bme, 0, false}, + {0x86, get_time, 0, false}, {0x87, set_time, 0, false}, + {0x99, set_flush, 0, false}}; static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table diff --git a/src/reset.cpp b/src/reset.cpp index 8c52bf35..a31d2c8e 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -69,12 +69,12 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { if ((!wakeup_sec) && (!wakeup_gpio) && (RTC_runmode == RUNMODE_NORMAL)) return; -// assure LMIC is in safe state +// wait until LMIC is in safe state before going to sleep #if (HAS_LORA) - if (os_queryTimeCriticalJobs(ms2osticks(10000))) - return; + while (os_queryTimeCriticalJobs(ms2osticks(wakeup_sec * 1000))) + vTaskDelay(pdMS_TO_TICKS(100)); - // to be done: save LoRaWAN channel configuration here + // to be done: save current LoRaWAN configuration here #endif @@ -99,10 +99,15 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { dp_shutdown(); #endif -// switch off wifi & ble +/* +// switch off radio #if (BLECOUNTER) - stop_BLEscan(); + btStop(); #endif +#if (WIFICOUNTER) + switch_wifi_sniffer(0); +#endif +*/ // reduce power if has PMU #ifdef HAS_PMU diff --git a/src/senddata.cpp b/src/senddata.cpp index 1cc09503..4ac5e67a 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -188,6 +188,10 @@ void sendData() { mask <<= 1; } // while (bitmask) + // goto sleep if we have a sleep cycle + if ((cfg.sleepcycle) && (RTC_runmode == RUNMODE_NORMAL)) + enter_deepsleep(cfg.sleepcycle * 2, HAS_BUTTON); + } // sendData() void flushQueues() { From 95cac817b4d6b8d6835fc4cd3c980c05c97d32b0 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Fri, 4 Dec 2020 19:09:35 +0100 Subject: [PATCH 009/104] sleep power optimization --- src/power.cpp | 2 +- src/reset.cpp | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index ee07aba9..3355bfa3 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -77,7 +77,7 @@ void AXP192_power(pmu_power_t powerlevel) { break; case pmu_power_sleep: - pmu.setChgLEDMode(AXP20X_LED_BLINK_1HZ); + pmu.setChgLEDMode(AXP20X_LED_OFF); // we don't cut off DCDC1, because then display blocks i2c bus pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // gps off pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // lora off diff --git a/src/reset.cpp b/src/reset.cpp index a31d2c8e..92a7af68 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -79,8 +79,8 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { #endif // set up power domains - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); - + //esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); + // set wakeup timer if (wakeup_sec) esp_sleep_enable_timer_wakeup(wakeup_sec * 1000000); @@ -99,15 +99,14 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { dp_shutdown(); #endif -/* // switch off radio #if (BLECOUNTER) + stop_BLEscan(); btStop(); #endif #if (WIFICOUNTER) switch_wifi_sniffer(0); #endif -*/ // reduce power if has PMU #ifdef HAS_PMU From b63b7f4ea2db2e29be0d14588960255cc6d7807d Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Sun, 6 Dec 2020 14:33:22 +0100 Subject: [PATCH 010/104] disable SD card reader on olimex --- src/hal/olimexpoeiso.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hal/olimexpoeiso.h b/src/hal/olimexpoeiso.h index 4fcbdd6b..70856336 100644 --- a/src/hal/olimexpoeiso.h +++ b/src/hal/olimexpoeiso.h @@ -8,7 +8,7 @@ #include // enable only if you want to store a local paxcount table on the device -#define HAS_SDCARD 2 // this board has a SDMMC card-reader/writer +//#define HAS_SDCARD 2 // this board has a SDMMC card-reader/writer // enable only if you want to send paxcount via ethernet port to mqtt server #define HAS_MQTT 1 // use MQTT on ethernet interface From b47c77ee990531e4a3c7f31b14ce280890b1de6f Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Sun, 6 Dec 2020 19:59:19 +0100 Subject: [PATCH 011/104] ttgobeam10.h: onboard LED added --- src/hal/ttgobeam10.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h index f7cae001..f6a44c32 100644 --- a/src/hal/ttgobeam10.h +++ b/src/hal/ttgobeam10.h @@ -13,7 +13,7 @@ for T-Beam version T22_V10 + T22_V11 pinouts taken from https://github.com/lewisxhe/TTGO-T-Beam /// Button functions: /// -Power, short press -> set device on (toggles display while device is on) +Power, short press -> set device on / while device is on: goto sleep Power, long press -> set device off User, short press -> flip display page User, long press -> send LORA message @@ -29,7 +29,7 @@ Reset -> reset device #define HAS_LORA 1 // comment out if device shall not send data via LoRa #define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define HAS_BUTTON GPIO_NUM_38 // middle on board button -#define HAS_LED NOT_A_PIN +#define HAS_LED GPIO_NUM_4 // not present on all T-Beam 1.0 boards // power management settings #define HAS_PMU 1 // has AXP192 chip From 016d69b5bba13b0026eb5b93b2a37dc3e7da19de Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Sun, 6 Dec 2020 22:06:45 +0100 Subject: [PATCH 012/104] ttgobeam10.h: LED active low correction --- src/hal/ttgobeam10.h | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h index f6a44c32..b2ff5b45 100644 --- a/src/hal/ttgobeam10.h +++ b/src/hal/ttgobeam10.h @@ -20,7 +20,7 @@ User, long press -> send LORA message Reset -> reset device */ -//#define HAS_DISPLAY 1 +#define HAS_DISPLAY 1 #define MY_DISPLAY_SDA SDA #define MY_DISPLAY_SCL SCL #define MY_DISPLAY_RST NOT_A_PIN @@ -30,6 +30,7 @@ Reset -> reset device #define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define HAS_BUTTON GPIO_NUM_38 // middle on board button #define HAS_LED GPIO_NUM_4 // not present on all T-Beam 1.0 boards +#define LED_ACTIVE_LOW 1 // power management settings #define HAS_PMU 1 // has AXP192 chip @@ -48,9 +49,9 @@ Reset -> reset device // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -//#define HAS_BME 1 // Enable BME sensors in general -//#define HAS_BME680 SDA, SCL -//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! +#define HAS_BME 1 // Enable BME sensors in general +#define HAS_BME680 SDA, SCL +#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! //#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature From 36afe66df90e61b98d19bccd394752bce30c9164 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 9 Dec 2020 10:15:12 +0100 Subject: [PATCH 013/104] new feature deep sleep (wokring alpha) --- include/lorawan.h | 6 +- include/senddata.h | 3 +- platformio_orig.ini | 17 ++- src/irqhandler.cpp | 7 ++ src/lorawan.cpp | 237 ++++++++++++++++++++++----------------- src/main.cpp | 4 +- src/mqttclient.cpp | 22 ++-- src/paxcounter_orig.conf | 13 ++- src/power.cpp | 2 +- src/reset.cpp | 95 ++++++++++------ src/senddata.cpp | 23 +++- src/spislave.cpp | 12 +- 12 files changed, 262 insertions(+), 179 deletions(-) diff --git a/include/lorawan.h b/include/lorawan.h index 3e54175d..e2239739 100644 --- a/include/lorawan.h +++ b/include/lorawan.h @@ -20,9 +20,10 @@ extern TaskHandle_t lmicTask, lorasendTask; -void lora_stack_reset(); -esp_err_t lora_stack_init(bool do_join); +esp_err_t lmic_init(void); void lora_setupForNetwork(bool preJoin); +void SaveLMICToRTC(int deepsleep_sec); +void LoadLMICFromRTC(); void lmictask(void *pvParameters); void gen_lora_deveui(uint8_t *pdeveui); void RevBytes(unsigned char *b, size_t c); @@ -33,6 +34,7 @@ void os_getDevEui(u1_t *buf); void lora_send(void *pvParameters); void lora_enqueuedata(MessageBuffer_t *message); void lora_queuereset(void); +uint32_t lora_queuewaiting(void); uint8_t myBattLevelCb(void *pUserData); void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev); void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, diff --git a/include/senddata.h b/include/senddata.h index dcebb809..008e439c 100644 --- a/include/senddata.h +++ b/include/senddata.h @@ -18,7 +18,8 @@ extern Ticker sendTimer; void SendPayload(uint8_t port, sendprio_t prio); void sendData(void); void checkSendQueues(void); -void flushQueues(); +void flushQueues(void); +bool allQueuesEmtpy(void); void setSendIRQ(void); #endif // _SENDDATA_H_ diff --git a/platformio_orig.ini b/platformio_orig.ini index def51a91..883960b2 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -7,7 +7,7 @@ ; ---> SELECT THE TARGET PLATFORM HERE! <--- [board] -halfile = generic.h +;halfile = generic.h ;halfile = ebox.h ;halfile = eboxtube.h ;halfile = ecopower.h @@ -19,7 +19,7 @@ halfile = generic.h ;halfile = ttgov21new.h ;halfile = ttgofox.h ;halfile = ttgobeam.h -;halfile = ttgobeam10.h +halfile = ttgobeam10.h ;halfile = ttgotdisplay.h ;halfile = fipy.h ;halfile = lopy.h @@ -47,7 +47,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 2.0.4 +release_version = 2.0.51 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 @@ -55,7 +55,7 @@ extra_scripts = pre:build.py otakeyfile = ota.conf lorakeyfile = loraconf.h lmicconfigfile = lmic_config.h -platform_espressif32 = espressif32@2.0.0 +platform_espressif32 = espressif32@2.1.0 monitor_speed = 115200 upload_speed = 115200 ; set by build.py and taken from hal file display_library = ; set by build.py and taken from hal file @@ -65,7 +65,7 @@ lib_deps_display = bitbank2/OneBitDisplay @ 1.9.0 bitbank2/BitBang_I2C @ ^2.1.3 ricmoo/QRCode @ ^0.0.1 - bodmer/TFT_eSPI @ ^2.2.20 + bodmer/TFT_eSPI @ ^2.3.51 lib_deps_ledmatrix = seeed-studio/Ultrathin_LED_Matrix @ ^1.0.0 lib_deps_rgbled = @@ -76,8 +76,7 @@ lib_deps_sensors = adafruit/Adafruit Unified Sensor @ ^1.1.4 adafruit/Adafruit BME280 Library @ ^2.1.1 adafruit/Adafruit BMP085 Library @ ^1.1.0 - ;boschsensortec/BSEC Software Library @ 1.6.1480 - https://github.com/BoschSensortec/BSEC-Arduino-library.git + boschsensortec/BSEC Software Library @ 1.6.1480 https://github.com/ricki-z/SDS011.git lib_deps_basic = bblanchon/ArduinoJson @ <6 @@ -117,7 +116,7 @@ framework = arduino board = esp32dev board_build.partitions = min_spiffs.csv upload_speed = ${common.upload_speed} -;upload_port = COM8 +;upload_port = COM3 platform = ${common.platform_espressif32} lib_deps = ${common.lib_deps_all} build_flags = ${common.build_flags_all} @@ -136,4 +135,4 @@ upload_protocol = esptool upload_protocol = esptool build_type = debug platform = https://github.com/platformio/platform-espressif32.git#develop -platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git +platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git \ No newline at end of file diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index 82b1690c..e8316119 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -95,6 +95,13 @@ void irqHandler(void *pvParameters) { if (InterruptStatus & SENDCYCLE_IRQ) { sendData(); InterruptStatus &= ~SENDCYCLE_IRQ; + // goto sleep if we have a sleep cycle + if (cfg.sleepcycle) +#ifdef HAS_BUTTON + enter_deepsleep(cfg.sleepcycle * 2, HAS_BUTTON); +#else + enter_deepsleep(cfg.sleepcycle * 2); +#endif } } // for } // irqHandler() diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 97865bf6..f2c75a0f 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -6,6 +6,9 @@ // Local logging Tag static const char TAG[] = "lora"; +// Saves the LMIC structure during deep sleep +RTC_DATA_ATTR lmic_t RTC_LMIC; + #if CLOCK_ERROR_PROCENTAGE > 7 #warning CLOCK_ERROR_PROCENTAGE value in lmic_config.h is too high; values > 7 will cause side effects #endif @@ -16,11 +19,6 @@ static const char TAG[] = "lora"; #endif #endif -// variable keep its values after restart or wakeup from sleep -RTC_NOINIT_ATTR u4_t RTCnetid, RTCdevaddr; -RTC_NOINIT_ATTR u1_t RTCnwkKey[16], RTCartKey[16]; -RTC_NOINIT_ATTR int RTCseqnoUp, RTCseqnoDn; - QueueHandle_t LoraSendQueue; TaskHandle_t lmicTask = NULL, lorasendTask = NULL; @@ -83,8 +81,6 @@ void lora_setupForNetwork(bool preJoin) { getSfName(updr2rps(LMIC.datarate)), getBwName(updr2rps(LMIC.datarate)), getCrName(updr2rps(LMIC.datarate))); - // store LMIC keys and counters in RTC memory - LMIC_getSessionKeys(&RTCnetid, &RTCdevaddr, RTCnwkKey, RTCartKey); } } @@ -197,59 +193,47 @@ void lora_send(void *pvParameters) { } // fetch next or wait for payload to send from queue - if (xQueueReceive(LoraSendQueue, &SendBuffer, portMAX_DELAY) != pdTRUE) { + // do not delete item from queue until it is transmitted + if (xQueuePeek(LoraSendQueue, &SendBuffer, portMAX_DELAY) != pdTRUE) { ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); continue; } // attempt to transmit payload - else { - - switch (LMIC_setTxData2_strict(SendBuffer.MessagePort, SendBuffer.Message, - SendBuffer.MessageSize, - (cfg.countermode & 0x02))) { - - case LMIC_ERROR_SUCCESS: - // save current Fcnt to RTC RAM - RTCseqnoUp = LMIC.seqnoUp; - RTCseqnoDn = LMIC.seqnoDn; + switch (LMIC_setTxData2_strict(SendBuffer.MessagePort, SendBuffer.Message, + SendBuffer.MessageSize, + (cfg.countermode & 0x02))) { + case LMIC_ERROR_SUCCESS: #if (TIME_SYNC_LORASERVER) - // if last packet sent was a timesync request, store TX timestamp - if (SendBuffer.MessagePort == TIMEPORT) - // store LMIC time when we started transmit of timesync request - timesync_store(osticks2ms(os_getTime()), timesync_tx); + // if last packet sent was a timesync request, store TX timestamp + if (SendBuffer.MessagePort == TIMEPORT) + // store LMIC time when we started transmit of timesync request + timesync_store(osticks2ms(os_getTime()), timesync_tx); #endif + ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize); + // delete sent item from queue + xQueueReceive(LoraSendQueue, &SendBuffer, (TickType_t)0); + break; + case LMIC_ERROR_TX_BUSY: // LMIC already has a tx message pending + case LMIC_ERROR_TX_FAILED: // message was not sent + vTaskDelay(pdMS_TO_TICKS(500 + random(400))); // wait a while + break; + case LMIC_ERROR_TX_TOO_LARGE: // message size exceeds LMIC buffer size + case LMIC_ERROR_TX_NOT_FEASIBLE: // message too large for current + // datarate + ESP_LOGI(TAG, "Message too large to send, message not sent and deleted"); + // we need some kind of error handling here -> to be done + break; + default: // other LMIC return code + ESP_LOGE(TAG, "LMIC error, message not sent and deleted"); - ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize); - break; - case LMIC_ERROR_TX_BUSY: // LMIC already has a tx message pending - case LMIC_ERROR_TX_FAILED: // message was not sent - // ESP_LOGD(TAG, "LMIC busy, message re-enqueued"); // very noisy - vTaskDelay(pdMS_TO_TICKS(1000 + random(500))); // wait a while - lora_enqueuedata(&SendBuffer); // re-enqueue the undelivered message - break; - case LMIC_ERROR_TX_TOO_LARGE: // message size exceeds LMIC buffer size - case LMIC_ERROR_TX_NOT_FEASIBLE: // message too large for current - // datarate - ESP_LOGI(TAG, - "Message too large to send, message not sent and deleted"); - // we need some kind of error handling here -> to be done - break; - default: // other LMIC return code - ESP_LOGE(TAG, "LMIC error, message not sent and deleted"); - - } // switch - } + } // switch delay(2); // yield to CPU - } + } // while(1) } -void lora_stack_reset() { - LMIC_reset(); // reset LMIC MAC -} - -esp_err_t lora_stack_init(bool do_join) { +esp_err_t lmic_init(void) { _ASSERT(SEND_QUEUE_SIZE > 0); LoraSendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t)); if (LoraSendQueue == 0) { @@ -259,7 +243,58 @@ esp_err_t lora_stack_init(bool do_join) { ESP_LOGI(TAG, "LORA send queue created, size %d Bytes", SEND_QUEUE_SIZE * sizeof(MessageBuffer_t)); - // start lorawan stack + // setup LMIC stack + os_init_ex(&myPinmap); // initialize lmic run-time environment + + // register a callback for downlink messages and lmic events. + // We aren't trying to write reentrant code, so pUserData is NULL. + // LMIC_reset() doesn't affect callbacks, so we can do this first. + LMIC_registerRxMessageCb(myRxCallback, NULL); + LMIC_registerEventCb(myEventCallback, NULL); + // to come with future LMIC version + // LMIC_registerBattLevelCb(myBattLevelCb, NULL); + + // Reset the MAC state. Session and pending data transfers will be + // discarded. + LMIC_reset(); + +// This tells LMIC to make the receive windows bigger, in case your clock is +// faster or slower. This causes the transceiver to be earlier switched on, +// so consuming more power. You may sharpen (reduce) CLOCK_ERROR_PERCENTAGE +// in src/lmic_config.h if you are limited on battery. +#ifdef CLOCK_ERROR_PROCENTAGE + LMIC_setClockError(CLOCK_ERROR_PROCENTAGE * MAX_CLOCK_ERROR / 1000); +#endif + +// Pass ABP parameters to LMIC_setSession +#ifdef LORA_ABP + setABPParameters(); // These parameters are defined as macro in loraconf.h + + // load saved session from RTC, if we have one + if (RTC_runmode == RUNMODE_WAKEUP) { + LoadLMICFromRTC(); + } else { + uint8_t appskey[sizeof(APPSKEY)]; + uint8_t nwkskey[sizeof(NWKSKEY)]; + memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); + memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); + LMIC_setSession(NETID, DEVADDR, nwkskey, appskey); + } + + // Pass OTA parameters to LMIC_setSession +#else + // load saved session from RTC, if we have one + if (RTC_runmode == RUNMODE_WAKEUP) { + LoadLMICFromRTC(); + } + // otherwise start join procedure if not already joined + else { + if (!LMIC_startJoining()) + ESP_LOGI(TAG, "Already joined"); + } +#endif + + // start lmic loop task ESP_LOGI(TAG, "Starting LMIC..."); xTaskCreatePinnedToCore(lmictask, // task function "lmictask", // name of task @@ -269,31 +304,7 @@ esp_err_t lora_stack_init(bool do_join) { &lmicTask, // task handle 1); // CPU core -#ifdef LORA_ABP - // Pass ABP parameters to LMIC_setSession - lora_stack_reset(); - uint8_t appskey[sizeof(APPSKEY)]; - uint8_t nwkskey[sizeof(NWKSKEY)]; - memcpy_P(appskey, APPSKEY, sizeof(APPSKEY)); - memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY)); - LMIC_setSession(NETID, DEVADDR, nwkskey, appskey); - // These parameters are defined as macro in loraconf.h - setABPParameters(); -#else - // Start join procedure if not already joined, - // lora_setupForNetwork(true) is called by eventhandler when joined - // else continue current session - if (do_join) { - if (!LMIC_startJoining()) - ESP_LOGI(TAG, "Already joined"); - } else { - lora_stack_reset(); - LMIC_setSession(RTCnetid, RTCdevaddr, RTCnwkKey, RTCartKey); - LMIC.seqnoUp = RTCseqnoUp; - LMIC.seqnoDn = RTCseqnoDn; - } -#endif - // start lmic send task + // start lora send task xTaskCreatePinnedToCore(lora_send, // task function "lorasendtask", // name of task 3072, // stack size of task @@ -319,11 +330,11 @@ void lora_enqueuedata(MessageBuffer_t *message) { ESP_LOGW(TAG, "LORA sendqueue purged, data is lost"); } case prio_normal: - ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0); + ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); break; case prio_low: default: - ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); + ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0); break; } if (ret != pdTRUE) { @@ -338,38 +349,18 @@ void lora_enqueuedata(MessageBuffer_t *message) { void lora_queuereset(void) { xQueueReset(LoraSendQueue); } -// LMIC lorawan stack task +uint32_t lora_queuewaiting(void) { + return uxQueueMessagesWaiting(LoraSendQueue); +} + +// LMIC loop task void lmictask(void *pvParameters) { _ASSERT((uint32_t)pvParameters == 1); - - // setup LMIC stack - os_init_ex(&myPinmap); // initialize lmic run-time environment - - // register a callback for downlink messages and lmic events. - // We aren't trying to write reentrant code, so pUserData is NULL. - // LMIC_reset() doesn't affect callbacks, so we can do this first. - LMIC_registerRxMessageCb(myRxCallback, NULL); - LMIC_registerEventCb(myEventCallback, NULL); - // to come with future LMIC version - // LMIC_registerBattLevelCb(myBattLevelCb, NULL); - - // Reset the MAC state. Session and pending data transfers will be - // discarded. - lora_stack_reset(); - -// This tells LMIC to make the receive windows bigger, in case your clock is -// faster or slower. This causes the transceiver to be earlier switched on, -// so consuming more power. You may sharpen (reduce) CLOCK_ERROR_PERCENTAGE -// in src/lmic_config.h if you are limited on battery. -#ifdef CLOCK_ERROR_PROCENTAGE - LMIC_setClockError(CLOCK_ERROR_PROCENTAGE * MAX_CLOCK_ERROR / 1000); -#endif - while (1) { os_runloop_once(); // execute lmic scheduled jobs and events delay(2); // yield to CPU } -} // lmictask +} // lmic event handler void myEventCallback(void *pUserData, ev_t ev) { @@ -410,7 +401,7 @@ void myEventCallback(void *pUserData, ev_t ev) { case EV_JOIN_FAILED: // must call LMIC_reset() to stop joining // otherwise join procedure continues. - lora_stack_reset(); + LMIC_reset(); break; case EV_JOIN_TXCOMPLETE: @@ -560,4 +551,44 @@ void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, bool is_down) { } // mac_decode() #endif // VERBOSE +// following code snippet was taken from +// https://github.com/JackGruber/ESP32-LMIC-DeepSleep-example/blob/master/src/main.cpp + +void SaveLMICToRTC(int deepsleep_sec) { + RTC_LMIC = LMIC; + + // ESP32 can't track millis during DeepSleep and no option to advance + // millis after DeepSleep. Therefore reset DutyCyles + + unsigned long now = millis(); + + // EU Like Bands +#if defined(CFG_LMIC_EU_like) + for (int i = 0; i < MAX_BANDS; i++) { + ostime_t correctedAvail = + RTC_LMIC.bands[i].avail - + ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC); + if (correctedAvail < 0) { + correctedAvail = 0; + } + RTC_LMIC.bands[i].avail = correctedAvail; + } + + RTC_LMIC.globalDutyAvail = RTC_LMIC.globalDutyAvail - + ((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC); + if (RTC_LMIC.globalDutyAvail < 0) { + RTC_LMIC.globalDutyAvail = 0; + } +#else + ESP_LOGW(TAG, "No DutyCycle recalculation function!"); +#endif + + ESP_LOGI(TAG, "LMIC state saved"); +} + +void LoadLMICFromRTC() { + LMIC = RTC_LMIC; + ESP_LOGI(TAG, "LMIC state loaded"); +} + #endif // HAS_LORA \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 687a926d..ea6206ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -348,9 +348,7 @@ void setup() { // initialize LoRa #if (HAS_LORA) strcat_P(features, " LORA"); - // kick off join, except we come from sleep - _ASSERT(lora_stack_init(RTC_runmode == RUNMODE_WAKEUP ? false : true) == - ESP_OK); + _ASSERT(lmic_init() == ESP_OK); #endif // initialize SPI diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 511f02b5..eb647c0c 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -115,7 +115,8 @@ void mqtt_client_task(void *param) { while (1) { // fetch next or wait for payload to send from queue - if (xQueueReceive(MQTTSendQueue, &msg, portMAX_DELAY) != pdTRUE) { + // do not delete item from queue until it is transmitted + if (xQueuePeek(MQTTSendQueue, &msg, portMAX_DELAY) != pdTRUE) { ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); continue; } @@ -129,19 +130,17 @@ void mqtt_client_task(void *param) { if (mqttClient.publish(MQTT_OUTTOPIC, buffer)) { ESP_LOGI(TAG, "%d byte(s) sent to MQTT server", msg.MessageSize + 2); + // delete sent item from queue + xQueueReceive(MQTTSendQueue, &msg, (TickType_t)0); continue; - } else { - mqtt_enqueuedata(&msg); // postpone the undelivered message - ESP_LOGD(TAG, - "Couldn't sent message to MQTT server, message postponed"); - } + } else + ESP_LOGD(TAG, "Couldn't sent message to MQTT server"); } else { // attempt to reconnect to MQTT server ESP_LOGD(TAG, "MQTT client reconnecting..."); ESP_LOGD(TAG, "MQTT last_error = %d / rc = %d", mqttClient.lastError(), mqttClient.returnCode()); - mqtt_enqueuedata(&msg); // postpone the undelivered message delay(MQTT_RETRYSEC * 1000); mqtt_connect(MQTT_SERVER, MQTT_PORT); } @@ -161,11 +160,11 @@ void mqtt_enqueuedata(MessageBuffer_t *message) { if (!uxQueueSpacesAvailable(MQTTSendQueue)) xQueueReceive(MQTTSendQueue, &DummyBuffer, (TickType_t)0); case prio_normal: - ret = xQueueSendToFront(MQTTSendQueue, (void *)message, (TickType_t)0); + ret = xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0); break; case prio_low: default: - ret = xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0); + ret = xQueueSendToFront(MQTTSendQueue, (void *)message, (TickType_t)0); break; } if (ret != pdTRUE) @@ -185,6 +184,11 @@ void mqtt_loop(void) { } void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); } + +uint32_t mqtt_queuewaiting(void) { + return uxQueueMessagesWaitingMQTTSendQueue); +} + void setMqttIRQ(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); } #endif // HAS_MQTT \ No newline at end of file diff --git a/src/paxcounter_orig.conf b/src/paxcounter_orig.conf index 0a7b88d1..c0dc5816 100644 --- a/src/paxcounter_orig.conf +++ b/src/paxcounter_orig.conf @@ -11,12 +11,13 @@ // Payload send cycle and encoding #define SENDCYCLE 30 // payload send cycle [seconds/2], 0 .. 255 +#define SLEEPCYCLE 0 // sleep time after a send cycle [seconds/2], 0 .. 255; 0 means no sleep [default = 0] #define PAYLOAD_ENCODER 2 // payload encoder: 1=Plain, 2=Packed, 3=Cayenne LPP dynamic, 4=Cayenne LPP packed #define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed // MAC sniffing parameters #define VENDORFILTER 0 // set to 0 if you want to scan all devices, not filtering smartphone OUIs -#define BLECOUNTER 0 // set to 0 if you do not want to install the BLE sniffer +#define BLECOUNTER 1 // set to 0 if you do not want to install the BLE sniffer #define WIFICOUNTER 1 // set to 0 if you do not want to install the WIFI sniffer #define MAC_QUEUE_SIZE 50 // size of MAC processing buffer (number of MACs) [default = 50] @@ -26,11 +27,11 @@ #define BLESCANINTERVAL 80 // [illiseconds] scan interval, see below, 3 .. 10240, default 80ms = 100% duty cycle // Corona Exposure Notification Service(ENS) counter -#define COUNT_ENS 0 // count found number of devices which advertise Exposure Notification Service - // set to 0 if you do not want to enable this function +#define COUNT_ENS 1 // count found number of devices which advertise Exposure Notification Service + // set to 1 if you want to enable this function [default=0] // for additional sensors (added by some user) -#define HAS_SENSOR_1 0 // set to 1 to enable data transfer of user sensor #1 (also used as ENS counter) [default=0] +#define HAS_SENSOR_1 1 // set to 1 to enable data transfer of user sensor #1 (also used as ENS counter) [default=0] #define HAS_SENSOR_2 0 // set to 1 to enable data transfer of user sensor #2 [default=0] #define HAS_SENSOR_3 0 // set to 1 to enable data transfer of user sensor #3 [default=0] @@ -83,10 +84,10 @@ #define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds] // settings for syncing time of node with a time source (network / gps / rtc / timeserver) -#define TIME_SYNC_LORAWAN 1 // set to 1 to use LORA network as time source, 0 means off [default = 1] +#define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 1] #define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0] #define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source [default = 60], 0 means off -#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off +#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off #define TIME_SYNC_SAMPLES 1 // number of time requests for averaging, max. 255 #define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds] #define TIME_SYNC_TIMEOUT 400 // timeout waiting for timeserver answer [seconds] diff --git a/src/power.cpp b/src/power.cpp index 3355bfa3..9b16995e 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -48,7 +48,7 @@ void AXP192_powerevent_IRQ(void) { ESP_LOGI(TAG, "Battery low temperature."); // short press -> esp32 deep sleep mode, can be exited by pressing user button - if (pmu.isPEKShortPressIRQ() && (RTC_runmode == RUNMODE_NORMAL)) { + if (pmu.isPEKShortPressIRQ()) { enter_deepsleep(0, HAS_BUTTON); } diff --git a/src/reset.cpp b/src/reset.cpp index 92a7af68..fe13a66e 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -5,21 +5,27 @@ // Local logging tag static const char TAG[] = __FILE__; +// Conversion factor for micro seconds to seconds +#define uS_TO_S_FACTOR 1000000ULL + // variable keep its values after restart or wakeup from sleep RTC_NOINIT_ATTR runmode_t RTC_runmode; +const char *runmode[4] = {"powercycle", "normal", "wakeup", "update"}; + void do_reset(bool warmstart) { if (warmstart) { - // store LMIC keys and counters in RTC memory - ESP_LOGI(TAG, "restarting device (warmstart), keeping runmode %d", - RTC_runmode); + ESP_LOGI(TAG, "restarting device (warmstart), keeping runmode %s", + runmode[RTC_runmode]); } else { #if (HAS_LORA) - if (RTC_runmode == RUNMODE_NORMAL) + if (RTC_runmode == RUNMODE_NORMAL) { LMIC_shutdown(); + } #endif RTC_runmode = RUNMODE_POWERCYCLE; - ESP_LOGI(TAG, "restarting device (coldstart), set runmode %d", RTC_runmode); + ESP_LOGI(TAG, "restarting device (coldstart), setting runmode %s", + runmode[RTC_runmode]); } esp_restart(); } @@ -40,9 +46,6 @@ void do_after_reset(int reason) { case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core RTC_runmode = RUNMODE_WAKEUP; -#if (HAS_LORA) - // to be done: restore LoRaWAN channel configuration and datarate here -#endif break; case SW_RESET: // 0x03 Software reset digital core @@ -61,32 +64,61 @@ void do_after_reset(int reason) { break; } - ESP_LOGI(TAG, "Starting Software v%s, runmode %d", PROGVERSION, RTC_runmode); + ESP_LOGI(TAG, "Starting Software v%s, runmode %s", PROGVERSION, + runmode[RTC_runmode]); } -void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { +void enter_deepsleep(const int wakeup_sec = 60, + const gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { - if ((!wakeup_sec) && (!wakeup_gpio) && (RTC_runmode == RUNMODE_NORMAL)) - return; - -// wait until LMIC is in safe state before going to sleep + // ensure we are in normal runmode, not udpate or wakeup + if ((RTC_runmode != RUNMODE_NORMAL) #if (HAS_LORA) - while (os_queryTimeCriticalJobs(ms2osticks(wakeup_sec * 1000))) - vTaskDelay(pdMS_TO_TICKS(100)); - - // to be done: save current LoRaWAN configuration here + || (LMIC.opmode & (OP_JOINING | OP_REJOIN)) +#endif + ) { + ESP_LOGE(TAG, "Can't go to sleep now"); + return; + } else { + ESP_LOGI(TAG, "Attempting to sleep..."); + } + // switch off radio +#if (BLECOUNTER) + stop_BLEscan(); + btStop(); +#endif +#if (WIFICOUNTER) + switch_wifi_sniffer(0); #endif - // set up power domains - //esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); - - // set wakeup timer - if (wakeup_sec) - esp_sleep_enable_timer_wakeup(wakeup_sec * 1000000); + // wait until all send queues are empty + ESP_LOGI(TAG, "Waiting until send queues are empty..."); + while (!allQueuesEmtpy()) + vTaskDelay(pdMS_TO_TICKS(100)); - // set wakeup gpio - if (wakeup_gpio != NOT_A_PIN) { +#if (HAS_LORA) + // shutdown LMIC safely + ESP_LOGI(TAG, "Waiting until LMIC is idle..."); + while ((LMIC.opmode & OP_TXRXPEND) || + os_queryTimeCriticalJobs(sec2osticks(wakeup_sec))) + vTaskDelay(pdMS_TO_TICKS(100)); + + SaveLMICToRTC(wakeup_sec); +// vTaskDelete(lmicTask); +// LMIC_shutdown(); +#endif // (HAS_LORA) + + // set up RTC power domains + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); + + // set up RTC wakeup timer, if we have + if (wakeup_sec > 0) { + esp_sleep_enable_timer_wakeup(wakeup_sec * uS_TO_S_FACTOR); + } + + // set wakeup gpio, if we have + if (wakeup_gpio != GPIO_NUM_MAX) { rtc_gpio_isolate(wakeup_gpio); esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW); } @@ -99,15 +131,6 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { dp_shutdown(); #endif -// switch off radio -#if (BLECOUNTER) - stop_BLEscan(); - btStop(); -#endif -#if (WIFICOUNTER) - switch_wifi_sniffer(0); -#endif - // reduce power if has PMU #ifdef HAS_PMU AXP192_power(pmu_power_sleep); @@ -117,6 +140,6 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { i2c_deinit(); // enter sleep mode - ESP_LOGI(TAG, "Going to sleep..."); + ESP_LOGI(TAG, "Going to sleep, good bye."); esp_deep_sleep_start(); } \ No newline at end of file diff --git a/src/senddata.cpp b/src/senddata.cpp index 4ac5e67a..31a1096c 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -10,6 +10,8 @@ void setSendIRQ() { // put data to send in RTos Queues used for transmit over channels Lora and SPI void SendPayload(uint8_t port, sendprio_t prio) { + ESP_LOGD(TAG, "sending Payload for Port %d (prio %d)", port, prio); + MessageBuffer_t SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[] @@ -187,14 +189,9 @@ void sendData() { bitmask &= ~mask; mask <<= 1; } // while (bitmask) - - // goto sleep if we have a sleep cycle - if ((cfg.sleepcycle) && (RTC_runmode == RUNMODE_NORMAL)) - enter_deepsleep(cfg.sleepcycle * 2, HAS_BUTTON); - } // sendData() -void flushQueues() { +void flushQueues(void) { #if (HAS_LORA) lora_queuereset(); #endif @@ -205,3 +202,17 @@ void flushQueues() { mqtt_queuereset(); #endif } + +bool allQueuesEmtpy(void) { + uint32_t rc = 0; +#if (HAS_LORA) + rc += lora_queuewaiting(); +#endif +#ifdef HAS_SPI + rc += spi_queuewaiting(); +#endif +#ifdef HAS_MQTT + rc += mqtt_queuewaiting(); +#endif + return (rc == 0) ? true : false; +} diff --git a/src/spislave.cpp b/src/spislave.cpp index 3e581318..1555b5ec 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -57,7 +57,8 @@ void spi_slave_task(void *param) { memset(rxbuf, 0, sizeof(rxbuf)); // fetch next or wait for payload to send from queue - if (xQueueReceive(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) { + // do not delete item from queue until it is transmitted + if (xQueuePeek(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) { ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); continue; } @@ -95,6 +96,9 @@ void spi_slave_task(void *param) { ESP_LOG_BUFFER_HEXDUMP(TAG, rxbuf, transaction_size, ESP_LOG_DEBUG); ESP_LOGI(TAG, "Transaction finished with size %zu bits", spi_transaction.trans_len); + + // delete sent item from queue + xQueueReceive(SPISendQueue, &msg, (TickType_t)0); // check if command was received, then call interpreter with command payload if ((spi_transaction.trans_len) && ((rxbuf[2]) == RCMDPORT)) { @@ -159,11 +163,11 @@ void spi_enqueuedata(MessageBuffer_t *message) { if (!uxQueueSpacesAvailable(SPISendQueue)) xQueueReceive(SPISendQueue, &DummyBuffer, (TickType_t)0); case prio_normal: - ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0); + ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); break; case prio_low: default: - ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); + ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0); break; } if (ret != pdTRUE) @@ -172,4 +176,6 @@ void spi_enqueuedata(MessageBuffer_t *message) { void spi_queuereset(void) { xQueueReset(SPISendQueue); } +uint32_t spi_queuewaiting(void) { return uxQueueMessagesWaiting(SPISendQueue); } + #endif // HAS_SPI \ No newline at end of file From 8db24bfa8e65bee69201554c10ee3ae99bf261dc Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 9 Dec 2020 11:01:54 +0100 Subject: [PATCH 014/104] msg prios removed, (not needed with buffer queues) --- include/globals.h | 2 -- include/senddata.h | 2 +- src/button.cpp | 2 +- src/lorawan.cpp | 24 +++--------------------- src/macsniff.cpp | 2 +- src/mqttclient.cpp | 19 +------------------ src/rcommand.cpp | 12 ++++++------ src/senddata.cpp | 21 ++++++++++----------- src/spislave.cpp | 21 ++------------------- src/timesync.cpp | 4 ++-- 10 files changed, 27 insertions(+), 82 deletions(-) diff --git a/include/globals.h b/include/globals.h index 5438fa3b..de59bac3 100644 --- a/include/globals.h +++ b/include/globals.h @@ -61,7 +61,6 @@ #define _micros() esp_timer_get_time() #define _seconds() _millis() / 1000.0 -enum sendprio_t { prio_low, prio_normal, prio_high }; enum timesource_t { _gps, _rtc, _lora, _unsynced }; enum snifftype_t { MAC_SNIFF_WIFI, MAC_SNIFF_BLE, MAC_SNIFF_BLE_ENS }; enum runmode_t { @@ -107,7 +106,6 @@ typedef struct __attribute__((packed)) { typedef struct { uint8_t MessageSize; uint8_t MessagePort; - sendprio_t MessagePrio; uint8_t Message[PAYLOAD_BUFFER_SIZE]; } MessageBuffer_t; diff --git a/include/senddata.h b/include/senddata.h index 008e439c..c99b1f5a 100644 --- a/include/senddata.h +++ b/include/senddata.h @@ -15,7 +15,7 @@ extern Ticker sendTimer; -void SendPayload(uint8_t port, sendprio_t prio); +void SendPayload(uint8_t port); void sendData(void); void checkSendQueues(void); void flushQueues(void); diff --git a/src/button.cpp b/src/button.cpp index dbd34b07..7a64bd07 100644 --- a/src/button.cpp +++ b/src/button.cpp @@ -33,7 +33,7 @@ void button_init(int pin) { b->setOnHolding([]() { payload.reset(); payload.addButton(0x01); - SendPayload(BUTTONPORT, prio_normal); + SendPayload(BUTTONPORT); }); // attach interrupt to the button diff --git a/src/lorawan.cpp b/src/lorawan.cpp index f2c75a0f..23d3f612 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -230,7 +230,7 @@ void lora_send(void *pvParameters) { } // switch delay(2); // yield to CPU - } // while(1) + } // while(1) } esp_err_t lmic_init(void) { @@ -318,26 +318,8 @@ esp_err_t lmic_init(void) { void lora_enqueuedata(MessageBuffer_t *message) { // enqueue message in LORA send queue - BaseType_t ret = pdFALSE; - MessageBuffer_t DummyBuffer; - sendprio_t prio = message->MessagePrio; - - switch (prio) { - case prio_high: - // clear some space in queue if full, then fallthrough to prio_normal - if (uxQueueSpacesAvailable(LoraSendQueue) == 0) { - xQueueReceive(LoraSendQueue, &DummyBuffer, (TickType_t)0); - ESP_LOGW(TAG, "LORA sendqueue purged, data is lost"); - } - case prio_normal: - ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); - break; - case prio_low: - default: - ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0); - break; - } - if (ret != pdTRUE) { + if (xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0) != + pdTRUE) { snprintf(lmic_event_msg + 14, LMIC_EVENTMSG_LEN - 14, "<>"); ESP_LOGW(TAG, "LORA sendqueue is full"); } else { diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 7a5063c7..7d0e4bea 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -126,7 +126,7 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { #endif payload.reset(); payload.addAlarm(MacBuffer.rssi, beaconID); - SendPayload(BEACONPORT, prio_high); + SendPayload(BEACONPORT); } }; diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index eb647c0c..3527e4c1 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -150,24 +150,7 @@ void mqtt_client_task(void *param) { void mqtt_enqueuedata(MessageBuffer_t *message) { // enqueue message in MQTT send queue - BaseType_t ret; - MessageBuffer_t DummyBuffer; - sendprio_t prio = message->MessagePrio; - - switch (prio) { - case prio_high: - // clear space in queue if full, then fallthrough to normal - if (!uxQueueSpacesAvailable(MQTTSendQueue)) - xQueueReceive(MQTTSendQueue, &DummyBuffer, (TickType_t)0); - case prio_normal: - ret = xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0); - break; - case prio_low: - default: - ret = xQueueSendToFront(MQTTSendQueue, (void *)message, (TickType_t)0); - break; - } - if (ret != pdTRUE) + if (xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0) != pdTRUE) ESP_LOGW(TAG, "MQTT sendqueue is full"); } diff --git a/src/rcommand.cpp b/src/rcommand.cpp index bd451d73..0295fab1 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -279,7 +279,7 @@ void get_config(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get device configuration"); payload.reset(); payload.addConfig(cfg); - SendPayload(CONFIGPORT, prio_high); + SendPayload(CONFIGPORT); }; void get_status(uint8_t val[]) { @@ -288,7 +288,7 @@ void get_status(uint8_t val[]) { payload.addStatus(read_voltage(), uptime() / 1000, temperatureRead(), getFreeRAM(), rtc_get_reset_reason(0), rtc_get_reset_reason(1)); - SendPayload(STATUSPORT, prio_high); + SendPayload(STATUSPORT); }; void get_gps(uint8_t val[]) { @@ -298,7 +298,7 @@ void get_gps(uint8_t val[]) { gps_storelocation(&gps_status); payload.reset(); payload.addGPS(gps_status); - SendPayload(GPSPORT, prio_high); + SendPayload(GPSPORT); #else ESP_LOGW(TAG, "GPS function not supported"); #endif @@ -309,7 +309,7 @@ void get_bme(uint8_t val[]) { #if (HAS_BME) payload.reset(); payload.addBME(bme_status); - SendPayload(BMEPORT, prio_high); + SendPayload(BMEPORT); #else ESP_LOGW(TAG, "BME sensor not supported"); #endif @@ -320,7 +320,7 @@ void get_batt(uint8_t val[]) { #if (defined BAT_MEASURE_ADC || defined HAS_PMU) payload.reset(); payload.addVoltage(read_voltage()); - SendPayload(BATTPORT, prio_normal); + SendPayload(BATTPORT); #else ESP_LOGW(TAG, "Battery voltage not supported"); #endif @@ -331,7 +331,7 @@ void get_time(uint8_t val[]) { payload.reset(); payload.addTime(now()); payload.addByte(timeStatus() << 4 | timeSource); - SendPayload(TIMEPORT, prio_high); + SendPayload(TIMEPORT); }; void set_time(uint8_t val[]) { diff --git a/src/senddata.cpp b/src/senddata.cpp index 31a1096c..6c10be82 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -8,15 +8,14 @@ void setSendIRQ() { } // put data to send in RTos Queues used for transmit over channels Lora and SPI -void SendPayload(uint8_t port, sendprio_t prio) { +void SendPayload(uint8_t port) { - ESP_LOGD(TAG, "sending Payload for Port %d (prio %d)", port, prio); + ESP_LOGD(TAG, "sending Payload for Port %d", port); MessageBuffer_t - SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[] + SendBuffer; // contains MessageSize, MessagePort, Message[] SendBuffer.MessageSize = payload.getSize(); - SendBuffer.MessagePrio = prio; switch (PAYLOAD_ENCODER) { case 1: // plain -> no mapping @@ -112,7 +111,7 @@ void sendData() { sds011_store(&sds_status); payload.addSDS(sds_status); #endif - SendPayload(COUNTERPORT, prio_normal); + SendPayload(COUNTERPORT); // clear counter if not in cumulative counter mode if (cfg.countermode != 1) { reset_counters(); // clear macs container and reset all counters @@ -130,7 +129,7 @@ void sendData() { case MEMS_DATA: payload.reset(); payload.addBME(bme_status); - SendPayload(BMEPORT, prio_normal); + SendPayload(BMEPORT); break; #endif @@ -142,7 +141,7 @@ void sendData() { gps_storelocation(&gps_status); payload.reset(); payload.addGPS(gps_status); - SendPayload(GPSPORT, prio_high); + SendPayload(GPSPORT); } else ESP_LOGD(TAG, "No valid GPS position"); } @@ -154,7 +153,7 @@ void sendData() { case SENSOR1_DATA: payload.reset(); payload.addSensor(sensor_read(1)); - SendPayload(SENSOR1PORT, prio_normal); + SendPayload(SENSOR1PORT); #if (COUNT_ENS) if (cfg.countermode != 1) cwa_clear(); @@ -165,14 +164,14 @@ void sendData() { case SENSOR2_DATA: payload.reset(); payload.addSensor(sensor_read(2)); - SendPayload(SENSOR2PORT, prio_normal); + SendPayload(SENSOR2PORT); break; #endif #if (HAS_SENSOR_3) case SENSOR3_DATA: payload.reset(); payload.addSensor(sensor_read(3)); - SendPayload(SENSOR3PORT, prio_normal); + SendPayload(SENSOR3PORT); break; #endif #endif @@ -181,7 +180,7 @@ void sendData() { case BATT_DATA: payload.reset(); payload.addVoltage(read_voltage()); - SendPayload(BATTPORT, prio_normal); + SendPayload(BATTPORT); break; #endif diff --git a/src/spislave.cpp b/src/spislave.cpp index 1555b5ec..bee8497e 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -96,7 +96,7 @@ void spi_slave_task(void *param) { ESP_LOG_BUFFER_HEXDUMP(TAG, rxbuf, transaction_size, ESP_LOG_DEBUG); ESP_LOGI(TAG, "Transaction finished with size %zu bits", spi_transaction.trans_len); - + // delete sent item from queue xQueueReceive(SPISendQueue, &msg, (TickType_t)0); @@ -153,24 +153,7 @@ esp_err_t spi_init() { void spi_enqueuedata(MessageBuffer_t *message) { // enqueue message in SPI send queue - BaseType_t ret; - MessageBuffer_t DummyBuffer; - sendprio_t prio = message->MessagePrio; - - switch (prio) { - case prio_high: - // clear space in queue if full, then fallthrough to normal - if (!uxQueueSpacesAvailable(SPISendQueue)) - xQueueReceive(SPISendQueue, &DummyBuffer, (TickType_t)0); - case prio_normal: - ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); - break; - case prio_low: - default: - ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0); - break; - } - if (ret != pdTRUE) + if (xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0) != pdTRUE) ESP_LOGW(TAG, "SPI sendqueue is full"); } diff --git a/src/timesync.cpp b/src/timesync.cpp index bdeea67b..c97da655 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -88,7 +88,7 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { #if (TIME_SYNC_LORASERVER) // ask user's timeserver (for LoRAWAN < 1.0.3) payload.reset(); payload.addByte(time_sync_seqNo); - SendPayload(TIMEPORT, prio_high); + SendPayload(TIMEPORT); #elif (TIME_SYNC_LORAWAN) // ask network (requires LoRAWAN >= 1.0.3) LMIC_requestNetworkTime(timesync_serverAnswer, &time_sync_seqNo); // trigger to immediately get DevTimeAns from class A device @@ -148,7 +148,7 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { // send timesync end char to show timesync was successful payload.reset(); payload.addByte(TIME_SYNC_END_FLAG); - SendPayload(TIMEPORT, prio_high); + SendPayload(TIMEPORT); goto Finish; Fail: From 22f0ce518478abe591619c67795490d4360950f7 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Wed, 9 Dec 2020 20:37:03 +0100 Subject: [PATCH 015/104] revert _millis & sanitizations deep sleep --- include/globals.h | 5 +-- include/reset.h | 4 +- src/bmesensor.cpp | 2 +- src/corona.cpp | 2 +- src/cyclic.cpp | 2 +- src/led.cpp | 14 +++---- src/main.cpp | 2 +- src/ota.cpp | 4 +- src/reset.cpp | 101 ++++++++++++++++++++++----------------------- src/timekeeper.cpp | 2 +- 10 files changed, 67 insertions(+), 71 deletions(-) diff --git a/include/globals.h b/include/globals.h index de59bac3..947066c8 100644 --- a/include/globals.h +++ b/include/globals.h @@ -56,10 +56,7 @@ ; \ } -// emulate millis to avoid rollovers -#define _millis() esp_timer_get_time() / 1000 -#define _micros() esp_timer_get_time() -#define _seconds() _millis() / 1000.0 +#define _seconds() millis() / 1000.0 enum timesource_t { _gps, _rtc, _lora, _unsynced }; enum snifftype_t { MAC_SNIFF_WIFI, MAC_SNIFF_BLE, MAC_SNIFF_BLE_ENS }; diff --git a/include/reset.h b/include/reset.h index be40bd3a..69735a77 100644 --- a/include/reset.h +++ b/include/reset.h @@ -10,7 +10,7 @@ #include "power.h" void do_reset(bool warmstart); -void do_after_reset(int reason); -void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio); +void do_after_reset(void); +void enter_deepsleep(const uint64_t wakeup_sec, const gpio_num_t wakeup_gpio); #endif // _RESET_H \ No newline at end of file diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 7d747c6d..4a365d90 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -228,7 +228,7 @@ void updateState(void) { } else { /* Update every STATE_SAVE_PERIOD minutes */ - if ((stateUpdateCounter * STATE_SAVE_PERIOD) < _millis()) { + if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) { update = true; stateUpdateCounter++; } diff --git a/src/corona.cpp b/src/corona.cpp index 8a2fd979..a8dd85a4 100644 --- a/src/corona.cpp +++ b/src/corona.cpp @@ -44,7 +44,7 @@ bool cwa_init(void) { } void cwa_mac_add(uint16_t hashedmac) { - cwaSeenNotifiers[hashedmac] = _millis(); // hash last seen at .... + cwaSeenNotifiers[hashedmac] = millis(); // hash last seen at .... } #endif diff --git a/src/cyclic.cpp b/src/cyclic.cpp index e65eaac2..2799b918 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -136,7 +136,7 @@ void doHousekeeping() { } // doHousekeeping() -uint64_t uptime() { return _millis(); } +uint64_t uptime() { return millis(); } uint32_t getFreeRAM() { #ifndef BOARD_HAS_PSRAM diff --git a/src/led.cpp b/src/led.cpp index a0708558..e74e92c6 100644 --- a/src/led.cpp +++ b/src/led.cpp @@ -9,7 +9,7 @@ led_states previousLEDState = TaskHandle_t ledLoopTask; uint16_t LEDColor = COLOR_NONE, LEDBlinkDuration = 0; // state machine variables -unsigned long LEDBlinkStarted = 0; // When (in _millis() led blink started) +unsigned long LEDBlinkStarted = 0; // When (in millis() led blink started) #ifdef HAS_RGB_LED @@ -133,7 +133,7 @@ void blink_LED(uint16_t set_color, uint16_t set_blinkduration) { #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) LEDColor = set_color; // set color for RGB LED LEDBlinkDuration = set_blinkduration; // duration - LEDBlinkStarted = _millis(); // Time Start here + LEDBlinkStarted = millis(); // Time Start here LEDState = LED_ON; // Let main set LED on #endif } @@ -145,8 +145,8 @@ void ledLoop(void *parameter) { // Custom blink running always have priority other LoRaWAN led // management if (LEDBlinkStarted && LEDBlinkDuration) { - // Custom blink is finished, let this order, avoid _millis() overflow - if ((_millis() - LEDBlinkStarted) >= LEDBlinkDuration) { + // Custom blink is finished, let this order, avoid millis() overflow + if ((millis() - LEDBlinkStarted) >= LEDBlinkDuration) { // Led becomes off, and stop blink LEDState = LED_OFF; LEDBlinkStarted = 0; @@ -165,7 +165,7 @@ void ledLoop(void *parameter) { LEDColor = COLOR_YELLOW; // quick blink 20ms on each 1/5 second LEDState = - ((_millis() % 200) < 20) ? LED_ON : LED_OFF; // TX data pending + ((millis() % 200) < 20) ? LED_ON : LED_OFF; // TX data pending } else if (LMIC.opmode & (OP_TXDATA | OP_TXRXPEND)) { // select color to blink by message port switch (LMIC.pendTxPort) { @@ -180,13 +180,13 @@ void ledLoop(void *parameter) { break; } // small blink 10ms on each 1/2sec (not when joining) - LEDState = ((_millis() % 500) < 10) ? LED_ON : LED_OFF; + LEDState = ((millis() % 500) < 10) ? LED_ON : LED_OFF; // This should not happen so indicate a problem } else if (LMIC.opmode & ((OP_TXDATA | OP_TXRXPEND | OP_JOINING | OP_REJOIN) == 0)) { LEDColor = COLOR_RED; // heartbeat long blink 200ms on each 2 seconds - LEDState = ((_millis() % 2000) < 200) ? LED_ON : LED_OFF; + LEDState = ((millis() % 2000) < 200) ? LED_ON : LED_OFF; } else #endif // HAS_LORA { diff --git a/src/main.cpp b/src/main.cpp index ea6206ba..65e13492 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -138,7 +138,7 @@ void setup() { esp_log_level_set("*", ESP_LOG_NONE); #endif - do_after_reset(rtc_get_reset_reason(0)); + do_after_reset(); // print chip information on startup if in verbose mode after coldstart #if (VERBOSE) diff --git a/src/ota.cpp b/src/ota.cpp index 70dea1a0..c256559e 100644 --- a/src/ota.cpp +++ b/src/ota.cpp @@ -178,9 +178,9 @@ int do_ota_update() { client.print("Cache-Control: no-cache\r\n"); client.print("Connection: close\r\n\r\n"); - unsigned long timeout = _millis(); + unsigned long timeout = millis(); while (client.available() == 0) { - if ((_millis() - timeout) > (RESPONSE_TIMEOUT_MS)) { + if ((millis() - timeout) > (RESPONSE_TIMEOUT_MS)) { ESP_LOGI(TAG, "Client timeout"); ota_display(3, " E", "client timeout"); goto abort; diff --git a/src/reset.cpp b/src/reset.cpp index fe13a66e..109c201f 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -8,8 +8,9 @@ static const char TAG[] = __FILE__; // Conversion factor for micro seconds to seconds #define uS_TO_S_FACTOR 1000000ULL -// variable keep its values after restart or wakeup from sleep -RTC_NOINIT_ATTR runmode_t RTC_runmode; +// variables keep its values after a wakeup from sleep +RTC_DATA_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; +static RTC_DATA_ATTR struct timeval RTC_sleep_start_time; const char *runmode[4] = {"powercycle", "normal", "wakeup", "update"}; @@ -30,45 +31,44 @@ void do_reset(bool warmstart) { esp_restart(); } -void do_after_reset(int reason) { +void do_after_reset(void) { - switch (reason) { + struct timeval sleep_stop_time; + uint64_t sleep_time_ms; - case POWERON_RESET: // 0x01 Vbat power on reset - case RTCWDT_BROWN_OUT_RESET: // 0x0f Reset when the vdd voltage is not - // stable - RTC_runmode = RUNMODE_POWERCYCLE; - break; + switch (esp_sleep_get_wakeup_cause()) { + case ESP_SLEEP_WAKEUP_EXT0: // Wakeup caused by external signal using RTC_IO + case ESP_SLEEP_WAKEUP_EXT1: // Wakeup caused by external signal using + // RTC_CNTL + case ESP_SLEEP_WAKEUP_TIMER: // Wakeup caused by timer + case ESP_SLEEP_WAKEUP_TOUCHPAD: // Wakeup caused by touchpad + case ESP_SLEEP_WAKEUP_ULP: // Wakeup caused by ULP program - case SW_CPU_RESET: // 0x0c Software reset CPU - // keep previous runmode (could be RUNMODE_UPDATE) - break; + // calculate time spent in deep sleep + gettimeofday(&sleep_stop_time, NULL); + sleep_time_ms = + (sleep_stop_time.tv_sec - RTC_sleep_start_time.tv_sec) * 1000 + + (sleep_stop_time.tv_usec - RTC_sleep_start_time.tv_usec) / 1000; + ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); - case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core RTC_runmode = RUNMODE_WAKEUP; break; - case SW_RESET: // 0x03 Software reset digital core - case OWDT_RESET: // 0x04 Legacy watch dog reset digital core - case SDIO_RESET: // 0x06 Reset by SLC module, reset digital core - case TG0WDT_SYS_RESET: // 0x07 Timer Group0 Watch dog reset digital core - case TG1WDT_SYS_RESET: // 0x08 Timer Group1 Watch dog reset digital core - case RTCWDT_SYS_RESET: // 0x09 RTC Watch dog Reset digital core - case INTRUSION_RESET: // 0x0a Instrusion tested to reset CPU - case TGWDT_CPU_RESET: // 0x0b Time Group reset CPU - case RTCWDT_CPU_RESET: // 0x0d RTC Watch dog Reset CPU - case EXT_CPU_RESET: // 0x0e for APP CPU, reseted by PRO CPU - case RTCWDT_RTC_RESET: // 0x10 RTC Watch dog reset digital core and rtc mode + case ESP_SLEEP_WAKEUP_ALL: + case ESP_SLEEP_WAKEUP_GPIO: + case ESP_SLEEP_WAKEUP_UART: + case ESP_SLEEP_WAKEUP_UNDEFINED: default: + // not a deep sleep reset RTC_runmode = RUNMODE_POWERCYCLE; break; - } + } // switch ESP_LOGI(TAG, "Starting Software v%s, runmode %s", PROGVERSION, runmode[RTC_runmode]); } -void enter_deepsleep(const int wakeup_sec = 60, +void enter_deepsleep(const uint64_t wakeup_sec = 60, const gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { // ensure we are in normal runmode, not udpate or wakeup @@ -83,15 +83,6 @@ void enter_deepsleep(const int wakeup_sec = 60, ESP_LOGI(TAG, "Attempting to sleep..."); } - // switch off radio -#if (BLECOUNTER) - stop_BLEscan(); - btStop(); -#endif -#if (WIFICOUNTER) - switch_wifi_sniffer(0); -#endif - // wait until all send queues are empty ESP_LOGI(TAG, "Waiting until send queues are empty..."); while (!allQueuesEmtpy()) @@ -105,23 +96,16 @@ void enter_deepsleep(const int wakeup_sec = 60, vTaskDelay(pdMS_TO_TICKS(100)); SaveLMICToRTC(wakeup_sec); -// vTaskDelete(lmicTask); -// LMIC_shutdown(); #endif // (HAS_LORA) - // set up RTC power domains - esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); - - // set up RTC wakeup timer, if we have - if (wakeup_sec > 0) { - esp_sleep_enable_timer_wakeup(wakeup_sec * uS_TO_S_FACTOR); - } - - // set wakeup gpio, if we have - if (wakeup_gpio != GPIO_NUM_MAX) { - rtc_gpio_isolate(wakeup_gpio); - esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW); - } +// switch off radio +#if (BLECOUNTER) + stop_BLEscan(); + btStop(); +#endif +#if (WIFICOUNTER) + switch_wifi_sniffer(0); +#endif // halt interrupts accessing i2c bus mask_user_IRQ(); @@ -139,7 +123,22 @@ void enter_deepsleep(const int wakeup_sec = 60, // shutdown i2c bus i2c_deinit(); - // enter sleep mode + // configure wakeup sources + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html + + // set up RTC wakeup timer, if we have + if (wakeup_sec > 0) { + esp_sleep_enable_timer_wakeup(wakeup_sec * uS_TO_S_FACTOR); + } + + // set wakeup gpio, if we have + if (wakeup_gpio != GPIO_NUM_MAX) { + rtc_gpio_isolate(wakeup_gpio); // minimize deep sleep current + esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW); + } + + // save sleep start time. Deep sleep. + gettimeofday(&RTC_sleep_start_time, NULL); ESP_LOGI(TAG, "Going to sleep, good bye."); esp_deep_sleep_start(); } \ No newline at end of file diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 1d4663ab..e4c08a89 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -26,7 +26,7 @@ Ticker timesyncer; void setTimeSyncIRQ() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); } void calibrateTime(void) { - ESP_LOGD(TAG, "[%0.3f] calibrateTime, timeSource == %d", _millis() / 1000.0, + ESP_LOGD(TAG, "[%0.3f] calibrateTime, timeSource == %d", millis() / 1000.0, timeSource); time_t t = 0; uint16_t t_msec = 0; From 61615d37e37495273a8aa9a00ddde4a29189cd5b Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 10 Dec 2020 15:09:24 +0100 Subject: [PATCH 016/104] mqttclient.cpp: fix hostname --- src/mqttclient.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 3527e4c1..7a1f3af7 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -42,8 +42,7 @@ esp_err_t mqtt_init(void) { int mqtt_connect(const char *my_host, const uint16_t my_port) { IPAddress mqtt_server_ip; - // static String clientId = "paxcounter-" + ETH.macAddress(); - static String clientId = "paxcounter-" + String(random(0xffff), HEX); + static String clientId = "paxcounter-" + ETH.macAddress(); ESP_LOGI(TAG, "MQTT name is %s", MQTT_CLIENTNAME); @@ -169,7 +168,7 @@ void mqtt_loop(void) { void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); } uint32_t mqtt_queuewaiting(void) { - return uxQueueMessagesWaitingMQTTSendQueue); + return uxQueueMessagesWaiting(MQTTSendQueue); } void setMqttIRQ(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); } From 258b6fce536c0c7fe8be6281f08677105e9f4cd1 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 10 Dec 2020 15:10:28 +0100 Subject: [PATCH 017/104] deep sleep further development --- include/mqttclient.h | 1 + include/spislave.h | 3 +- src/irqhandler.cpp | 2 +- src/reset.cpp | 78 +++++++++++++++++++++++++++++++------------- 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/include/mqttclient.h b/include/mqttclient.h index 9b7ac649..804ee0f3 100644 --- a/include/mqttclient.h +++ b/include/mqttclient.h @@ -25,6 +25,7 @@ extern TaskHandle_t mqttTask; void mqtt_enqueuedata(MessageBuffer_t *message); +uint32_t mqtt_queuewaiting(void); void mqtt_queuereset(void); void setMqttIRQ(void); void mqtt_loop(void); diff --git a/include/spislave.h b/include/spislave.h index 6db35d00..e4b71f02 100644 --- a/include/spislave.h +++ b/include/spislave.h @@ -32,6 +32,7 @@ esp_err_t spi_init(); extern TaskHandle_t spiTask; void spi_enqueuedata(MessageBuffer_t *message); -void spi_queuereset(); +uint32_t spi_queuewaiting(void); +void spi_queuereset(void); #endif // _SPISLAVE_H diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index e8316119..687a6965 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -98,7 +98,7 @@ void irqHandler(void *pvParameters) { // goto sleep if we have a sleep cycle if (cfg.sleepcycle) #ifdef HAS_BUTTON - enter_deepsleep(cfg.sleepcycle * 2, HAS_BUTTON); + enter_deepsleep(cfg.sleepcycle * 2, (gpio_num_t)HAS_BUTTON); #else enter_deepsleep(cfg.sleepcycle * 2); #endif diff --git a/src/reset.cpp b/src/reset.cpp index 109c201f..ff295baf 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -10,7 +10,8 @@ static const char TAG[] = __FILE__; // variables keep its values after a wakeup from sleep RTC_DATA_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; -static RTC_DATA_ATTR struct timeval RTC_sleep_start_time; +RTC_DATA_ATTR struct timeval RTC_sleep_start_time; +timeval sleep_stop_time; const char *runmode[4] = {"powercycle", "normal", "wakeup", "update"}; @@ -69,36 +70,28 @@ void do_after_reset(void) { } void enter_deepsleep(const uint64_t wakeup_sec = 60, - const gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { + gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { + + int i; + + // validate wake up pin, if we have + if (!GPIO_IS_VALID_GPIO(wakeup_gpio)) + wakeup_gpio = GPIO_NUM_MAX; // ensure we are in normal runmode, not udpate or wakeup if ((RTC_runmode != RUNMODE_NORMAL) #if (HAS_LORA) || (LMIC.opmode & (OP_JOINING | OP_REJOIN)) #endif - ) { - ESP_LOGE(TAG, "Can't go to sleep now"); + ) return; - } else { - ESP_LOGI(TAG, "Attempting to sleep..."); - } - // wait until all send queues are empty - ESP_LOGI(TAG, "Waiting until send queues are empty..."); - while (!allQueuesEmtpy()) - vTaskDelay(pdMS_TO_TICKS(100)); + ESP_LOGI(TAG, "Preparing to sleep..."); -#if (HAS_LORA) - // shutdown LMIC safely - ESP_LOGI(TAG, "Waiting until LMIC is idle..."); - while ((LMIC.opmode & OP_TXRXPEND) || - os_queryTimeCriticalJobs(sec2osticks(wakeup_sec))) - vTaskDelay(pdMS_TO_TICKS(100)); + // stop further enqueuing of senddata + sendTimer.detach(); - SaveLMICToRTC(wakeup_sec); -#endif // (HAS_LORA) - -// switch off radio + // switch off radio #if (BLECOUNTER) stop_BLEscan(); btStop(); @@ -107,6 +100,43 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, switch_wifi_sniffer(0); #endif + // wait a while (max 100 sec) to clear send queues + ESP_LOGI(TAG, "Waiting until send queues are empty..."); + for (i = 10; i > 0; i--) { + if (!allQueuesEmtpy()) + vTaskDelay(pdMS_TO_TICKS(10000)); + else + break; + } + if (i == 0) + goto Error; + + // shutdown LMIC safely, waiting max 100 sec +#if (HAS_LORA) + ESP_LOGI(TAG, "Waiting until LMIC is idle..."); + for (i = 10; i > 0; i--) { + if ((LMIC.opmode & OP_TXRXPEND) || + os_queryTimeCriticalJobs(sec2osticks(wakeup_sec))) + vTaskDelay(pdMS_TO_TICKS(10000)); + else + break; + } + if (i == 0) + goto Error; + + SaveLMICToRTC(wakeup_sec); +#endif // (HAS_LORA) + +// shutdown MQTT safely +#ifdef HAS_MQTT +// to come +#endif + +// shutdown SPI safely +#ifdef HAS_SPI +// to come +#endif + // halt interrupts accessing i2c bus mask_user_IRQ(); @@ -137,8 +167,12 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW); } - // save sleep start time. Deep sleep. + // time stamp sleep start time. Deep sleep. gettimeofday(&RTC_sleep_start_time, NULL); ESP_LOGI(TAG, "Going to sleep, good bye."); esp_deep_sleep_start(); + +Error: + ESP_LOGE(TAG, "Can't go to sleep. Resetting."); + do_reset(true); } \ No newline at end of file From 28d30493fde556cee758891049d79ea313a35434 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 10 Dec 2020 22:12:57 +0100 Subject: [PATCH 018/104] channel hopping switch on/off option --- README.md | 5 +++-- include/wifiscan.h | 2 ++ src/main.cpp | 4 ++-- src/rcommand.cpp | 23 ++++++++++++++++------- src/wifiscan.cpp | 19 +++++++++++++------ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a5f01596..658782f5 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering. byte 6: Counter mode (0=cyclic unconfirmed, 1=cumulative, 2=cyclic confirmed) [default 0] bytes 7-8: RSSI limiter threshold value (negative) [default 0] byte 9: Lora Payload send cycle in seconds/2 (0..255) [default 120] - byte 10: Wifi channel switch interval in seconds/100 (0..255) [default 50] + byte 10: Wifi channel hopping interval in seconds/100 (0..255), 0 means no hopping [default 50] byte 11: Bluetooth channel switch interval in seconds/100 (0..255) [efault 10] byte 12: Bluetooth scanner status (1=on, 0=0ff) [default 1] byte 13: Wifi antenna switch (0=internal, 1=external) [default 0] @@ -437,10 +437,11 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0 ... 255 payload send cycle in seconds/2 e.g. 120 -> payload is transmitted each 240 seconds [default] -0x0B set Wifi channel switch interval timer +0x0B set Wifi channel hopping interval timer 0 ... 255 duration for scanning a wifi channel in seconds/100 e.g. 50 -> each channel is scanned for 500 milliseconds [default] + 0 means no hopping, scanning on channel WIFI_CHANNEL_MIN only 0x0C set Bluetooth channel switch interval timer diff --git a/include/wifiscan.h b/include/wifiscan.h index 0b212a9d..53a414c3 100644 --- a/include/wifiscan.h +++ b/include/wifiscan.h @@ -8,6 +8,8 @@ #include "antenna.h" // code for switching wifi antennas #include "macsniff.h" +extern TimerHandle_t WifiChanTimer; + void wifi_sniffer_init(void); void switch_wifi_sniffer(uint8_t state); void IRAM_ATTR wifi_sniffer_packet_handler(void *buff, diff --git a/src/main.cpp b/src/main.cpp index 65e13492..ffaa544c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -88,8 +88,8 @@ triggers pps 1 sec impulse configData_t cfg; // struct holds current device configuration char lmic_event_msg[LMIC_EVENTMSG_LEN]; // display buffer for LMIC event message uint8_t batt_level = 0; // display value -uint8_t volatile channel = 0; // channel rotation counter -uint8_t volatile rf_load = 0; // RF traffic indicator +uint8_t volatile channel = WIFI_CHANNEL_MIN; // channel rotation counter +uint8_t volatile rf_load = 0; // RF traffic indicator uint16_t volatile macs_wifi = 0, macs_ble = 0; // globals for display hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL, *matrixDisplayIRQ = NULL; diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 0295fab1..791346e2 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -63,11 +63,20 @@ void set_sleepcycle(uint8_t val[]) { void set_wifichancycle(uint8_t val[]) { cfg.wifichancycle = val[0]; // update Wifi channel rotation timer period - xTimerChangePeriod(WifiChanTimer, pdMS_TO_TICKS(cfg.wifichancycle * 10), 100); - + if (cfg.wifichancycle > 0) { + if (xTimerIsTimerActive(WifiChanTimer) == pdFALSE) + xTimerStart(WifiChanTimer, (TickType_t) 0); + xTimerChangePeriod(WifiChanTimer, pdMS_TO_TICKS(cfg.wifichancycle * 10), + 100); ESP_LOGI(TAG, - "Remote command: set Wifi channel switch interval to %.1f seconds", + "Remote command: set Wifi channel hopping interval to %.1f seconds", cfg.wifichancycle / float(100)); + } else { + xTimerStop(WifiChanTimer, (TickType_t) 0); + esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); + channel = WIFI_CHANNEL_MIN; + ESP_LOGI(TAG, "Remote command: set Wifi channel hopping to off"); + } } void set_blescantime(uint8_t val[]) { @@ -404,10 +413,10 @@ void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { table[i].func( foundcmd); // execute assigned function with given parameters } else - ESP_LOGI( - TAG, - "Remote command x%02X called with missing parameter(s), skipped", - table[i].opcode); + ESP_LOGI(TAG, + "Remote command x%02X called with missing parameter(s), " + "skipped", + table[i].opcode); break; // command found -> exit table lookup loop } // end of command validation } // end of command table lookup loop diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index d6325fcd..3dc1bfeb 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -41,8 +41,6 @@ IRAM_ATTR void wifi_sniffer_packet_handler(void *buff, // Software-timer driven Wifi channel rotation callback function void switchWifiChannel(TimerHandle_t xTimer) { - // static uint8_t channel = 0; // channel rotation counter - _ASSERT(xTimer != NULL); channel = (channel % WIFI_CHANNEL_MAX) + 1; // rotate channel 1..WIFI_CHANNEL_MAX esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); @@ -74,20 +72,29 @@ void wifi_sniffer_init(void) { // setup wifi channel rotation timer WifiChanTimer = - xTimerCreate("WifiChannelTimer", pdMS_TO_TICKS(cfg.wifichancycle * 10), + xTimerCreate("WifiChannelTimer", + (cfg.wifichancycle > 0) ? pdMS_TO_TICKS(cfg.wifichancycle) + : pdMS_TO_TICKS(50), pdTRUE, (void *)0, switchWifiChannel); + if (cfg.wifichancycle > 0) + xTimerStart(WifiChanTimer, (TickType_t) 0); + else + esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); } void switch_wifi_sniffer(uint8_t state) { - _ASSERT(WifiChanTimer != NULL); if (state) { // switch wifi sniffer on ESP_ERROR_CHECK(esp_wifi_start()); - xTimerStart(WifiChanTimer, 0); + if (cfg.wifichancycle > 0) + xTimerStart(WifiChanTimer, (TickType_t) 0); + else + esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); esp_wifi_set_promiscuous(true); } else { // switch wifi sniffer off - xTimerStop(WifiChanTimer, 0); + if (xTimerIsTimerActive(WifiChanTimer) != pdFALSE) + xTimerStop(WifiChanTimer, (TickType_t) 0); esp_wifi_set_promiscuous(false); ESP_ERROR_CHECK(esp_wifi_stop()); macs_wifi = 0; // clear WIFI counter From 93315e142c621d74ecdc51cfbb1f0f9bee4e0973 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Thu, 10 Dec 2020 22:14:41 +0100 Subject: [PATCH 019/104] app irq off before sleep --- src/reset.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index ff295baf..bd0284d7 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -91,6 +91,19 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, // stop further enqueuing of senddata sendTimer.detach(); + // shutdown MQTT safely +#ifdef HAS_MQTT +// to come +#endif + +// shutdown SPI safely +#ifdef HAS_SPI +// to come +#endif + + // halt interrupts accessing i2c bus + mask_user_IRQ(); + // switch off radio #if (BLECOUNTER) stop_BLEscan(); @@ -127,19 +140,6 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, SaveLMICToRTC(wakeup_sec); #endif // (HAS_LORA) -// shutdown MQTT safely -#ifdef HAS_MQTT -// to come -#endif - -// shutdown SPI safely -#ifdef HAS_SPI -// to come -#endif - - // halt interrupts accessing i2c bus - mask_user_IRQ(); - // switch off display #ifdef HAS_DISPLAY dp_shutdown(); From d048b759a82778f9dfeb2d7a6d7246b765fddd95 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Fri, 11 Dec 2020 12:39:25 +0100 Subject: [PATCH 020/104] main.cpp: bugfix wifi start --- src/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 687a926d..6a6d2d0b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -417,7 +417,7 @@ void setup() { // start wifi in monitor mode and start channel rotation timer wifi_sniffer_init(); - if (cfg.blescan) { + if (cfg.wifiscan) { ESP_LOGI(TAG, "Starting Wifi..."); switch_wifi_sniffer(1); } else @@ -526,4 +526,4 @@ void setup() { } // setup() -void loop() { vTaskDelete(NULL); } \ No newline at end of file +void loop() { vTaskDelete(NULL); } From 7631a96cace9533c39b7fec9a3f71cd4b989def1 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Fri, 11 Dec 2020 13:47:35 +0100 Subject: [PATCH 021/104] olimexpoeiso.h: comments edited --- src/hal/olimexpoeiso.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hal/olimexpoeiso.h b/src/hal/olimexpoeiso.h index 70856336..dcc32e43 100644 --- a/src/hal/olimexpoeiso.h +++ b/src/hal/olimexpoeiso.h @@ -13,10 +13,10 @@ // enable only if you want to send paxcount via ethernet port to mqtt server #define HAS_MQTT 1 // use MQTT on ethernet interface -//#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7 +//#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery measurement //#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 470k/470k on board -#define BAT_MEASURE_ADC ADC1_GPIO39_CHANNEL // external power probe GPIO pin -#define BAT_VOLTAGE_DIVIDER 2.1277f // voltage divider 47k/442k on board +#define BAT_MEASURE_ADC ADC1_GPIO39_CHANNEL // external power sense +#define BAT_VOLTAGE_DIVIDER 2.1277f // voltage divider 47k/100k on board #define HAS_BUTTON KEY_BUILTIN // on board button #define HAS_LED NOT_A_PIN // no on board LED From b32e91317ffa8e27beac52a32aec1d812a1c63b5 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Fri, 11 Dec 2020 16:34:17 +0100 Subject: [PATCH 022/104] deep sleep further development --- include/globals.h | 3 +- include/rcommand.h | 4 ++- src/blecsan.cpp | 15 ++++++++-- src/display.cpp | 1 - src/main.cpp | 11 +++---- src/rcommand.cpp | 18 ++++++++---- src/reset.cpp | 71 +++++++++++++++++++++++++--------------------- src/wifiscan.cpp | 18 ++++++------ 8 files changed, 83 insertions(+), 58 deletions(-) diff --git a/include/globals.h b/include/globals.h index 947066c8..89b9ecca 100644 --- a/include/globals.h +++ b/include/globals.h @@ -64,7 +64,8 @@ enum runmode_t { RUNMODE_POWERCYCLE, RUNMODE_NORMAL, RUNMODE_WAKEUP, - RUNMODE_UPDATE + RUNMODE_UPDATE, + RUNMODE_SLEEP }; // Struct holding devices's runtime configuration diff --git a/include/rcommand.h b/include/rcommand.h index 488b7005..c26bce84 100644 --- a/include/rcommand.h +++ b/include/rcommand.h @@ -18,11 +18,13 @@ // table of remote commands and assigned functions typedef struct { const uint8_t opcode; - void (*func)(uint8_t []); + void (*func)(uint8_t[]); const uint8_t params; const bool store; } cmd_t; +extern bool rcmd_busy; + void rcommand(const uint8_t cmd[], const uint8_t cmdlength); void do_reset(bool warmstart); diff --git a/src/blecsan.cpp b/src/blecsan.cpp index b06e78a0..f39a2e58 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -254,8 +254,11 @@ void start_BLEscan(void) { // Initialize BT controller to allocate task and other resource. ESP_ERROR_CHECK(esp_coex_preference_set(ESP_COEX_PREFER_BT)); + if (!btStart()) { // enable bt_controller + ESP_LOGE(TAG, "Bluetooth controller start failed. Resetting device"); + do_reset(true); + } - btStart(); ESP_ERROR_CHECK(esp_bluedroid_init()); ESP_ERROR_CHECK(esp_bluedroid_enable()); @@ -269,10 +272,18 @@ void start_BLEscan(void) { void stop_BLEscan(void) { #if (BLECOUNTER) ESP_LOGI(TAG, "Shutting down bluetooth scanner ..."); + + ESP_LOGD(TAG, "unregister GAP callback..."); ESP_ERROR_CHECK(esp_ble_gap_register_callback(NULL)); + ESP_LOGD(TAG, "bluedroid disable..."); ESP_ERROR_CHECK(esp_bluedroid_disable()); + ESP_LOGD(TAG, "bluedroid deinit..."); ESP_ERROR_CHECK(esp_bluedroid_deinit()); - btStop(); // disable bt_controller + + if (!btStop()) { // disable bt_controller + ESP_LOGE(TAG, "Bluetooth controller stop failed. Resetting device"); + do_reset(true); + } ESP_ERROR_CHECK(esp_coex_preference_set(ESP_COEX_PREFER_WIFI)); ESP_LOGI(TAG, "Bluetooth scanner stopped"); #endif // BLECOUNTER diff --git a/src/display.cpp b/src/display.cpp index 43be8d80..4ccb1b7e 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -603,7 +603,6 @@ void dp_shutdown(void) { if (!I2C_MUTEX_LOCK()) ESP_LOGV(TAG, "[%0.3f] i2c mutex lock failed", _seconds()); else { - cfg.screenon = 0; obdPower(&ssoled, false); delay(DISPLAYREFRESH_MS / 1000 * 1.1); I2C_MUTEX_UNLOCK(); // release i2c bus access diff --git a/src/main.cpp b/src/main.cpp index ffaa544c..6bba631a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -292,7 +292,7 @@ void setup() { macQueueInit(); // start BLE scan callback if BLE function is enabled in NVRAM configuration -// or switch off bluetooth, if not compiled +// or remove bluetooth stack from RAM, if option bluetooth is not compiled #if (BLECOUNTER) strcat_P(features, " BLE"); if (cfg.blescan) { @@ -412,16 +412,17 @@ void setup() { #if (WIFICOUNTER) strcat_P(features, " WIFI"); - // start wifi in monitor mode and start channel rotation timer - + // install wifi driver in RAM and start channel hopping wifi_sniffer_init(); - if (cfg.blescan) { + + // start wifi sniffing, if enabled + if (cfg.wifiscan) { ESP_LOGI(TAG, "Starting Wifi..."); switch_wifi_sniffer(1); } else switch_wifi_sniffer(0); #else - // switch off wifi + // remove wifi driver from RAM, if option wifi not compiled esp_wifi_deinit(); #endif diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 791346e2..c18db679 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -5,6 +5,9 @@ // Local logging tag static const char TAG[] = __FILE__; +// global variable indicating if rcommand() is executing +bool rcmd_busy = false; + // set of functions that can be triggered by remote commands void set_reset(uint8_t val[]) { switch (val[0]) { @@ -65,14 +68,15 @@ void set_wifichancycle(uint8_t val[]) { // update Wifi channel rotation timer period if (cfg.wifichancycle > 0) { if (xTimerIsTimerActive(WifiChanTimer) == pdFALSE) - xTimerStart(WifiChanTimer, (TickType_t) 0); + xTimerStart(WifiChanTimer, (TickType_t)0); xTimerChangePeriod(WifiChanTimer, pdMS_TO_TICKS(cfg.wifichancycle * 10), 100); - ESP_LOGI(TAG, - "Remote command: set Wifi channel hopping interval to %.1f seconds", - cfg.wifichancycle / float(100)); + ESP_LOGI( + TAG, + "Remote command: set Wifi channel hopping interval to %.1f seconds", + cfg.wifichancycle / float(100)); } else { - xTimerStop(WifiChanTimer, (TickType_t) 0); + xTimerStop(WifiChanTimer, (TickType_t)0); esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); channel = WIFI_CHANNEL_MIN; ESP_LOGI(TAG, "Remote command: set Wifi channel hopping to off"); @@ -397,6 +401,7 @@ void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { uint8_t foundcmd[cmdlength], cursor = 0; bool storeflag = false; + rcmd_busy = true; while (cursor < cmdlength) { @@ -428,4 +433,7 @@ void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { if (storeflag) saveConfig(); + + rcmd_busy = false; + } // rcommand() diff --git a/src/reset.cpp b/src/reset.cpp index bd0284d7..c3983459 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -13,12 +13,11 @@ RTC_DATA_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; RTC_DATA_ATTR struct timeval RTC_sleep_start_time; timeval sleep_stop_time; -const char *runmode[4] = {"powercycle", "normal", "wakeup", "update"}; +const char *runmode[5] = {"powercycle", "normal", "wakeup", "update", "sleep"}; void do_reset(bool warmstart) { if (warmstart) { - ESP_LOGI(TAG, "restarting device (warmstart), keeping runmode %s", - runmode[RTC_runmode]); + ESP_LOGI(TAG, "restarting device (warmstart)"); } else { #if (HAS_LORA) if (RTC_runmode == RUNMODE_NORMAL) { @@ -26,8 +25,7 @@ void do_reset(bool warmstart) { } #endif RTC_runmode = RUNMODE_POWERCYCLE; - ESP_LOGI(TAG, "restarting device (coldstart), setting runmode %s", - runmode[RTC_runmode]); + ESP_LOGI(TAG, "restarting device (coldstart)"); } esp_restart(); } @@ -78,41 +76,16 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, if (!GPIO_IS_VALID_GPIO(wakeup_gpio)) wakeup_gpio = GPIO_NUM_MAX; - // ensure we are in normal runmode, not udpate or wakeup - if ((RTC_runmode != RUNMODE_NORMAL) -#if (HAS_LORA) - || (LMIC.opmode & (OP_JOINING | OP_REJOIN)) -#endif - ) - return; - ESP_LOGI(TAG, "Preparing to sleep..."); + RTC_runmode = RUNMODE_SLEEP; + // stop further enqueuing of senddata sendTimer.detach(); - // shutdown MQTT safely -#ifdef HAS_MQTT -// to come -#endif - -// shutdown SPI safely -#ifdef HAS_SPI -// to come -#endif - // halt interrupts accessing i2c bus mask_user_IRQ(); - // switch off radio -#if (BLECOUNTER) - stop_BLEscan(); - btStop(); -#endif -#if (WIFICOUNTER) - switch_wifi_sniffer(0); -#endif - // wait a while (max 100 sec) to clear send queues ESP_LOGI(TAG, "Waiting until send queues are empty..."); for (i = 10; i > 0; i--) { @@ -136,11 +109,43 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, } if (i == 0) goto Error; +#endif // (HAS_LORA) +// shutdown MQTT safely +#ifdef HAS_MQTT +// to come +#endif + +// shutdown SPI safely +#ifdef HAS_SPI +// to come +#endif + + // wait until rcommands are all done + for (i = 10; i > 0; i--) { + if (rcmd_busy) + vTaskDelay(pdMS_TO_TICKS(1000)); + else + break; + } + if (i == 0) + goto Error; + +// switch off radio +#if (WIFICOUNTER) + switch_wifi_sniffer(0); +#endif +#if (BLECOUNTER) + stop_BLEscan(); + btStop(); +#endif + + // save LMIC state to RTC RAM +#if (HAS_LORA) SaveLMICToRTC(wakeup_sec); #endif // (HAS_LORA) -// switch off display +// set display to power save mode #ifdef HAS_DISPLAY dp_shutdown(); #endif diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index 3dc1bfeb..ccb6a87e 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -70,33 +70,31 @@ void wifi_sniffer_init(void) { ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler)); ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); // now switch on monitor mode - // setup wifi channel rotation timer + // setup wifi channel hopping timer WifiChanTimer = xTimerCreate("WifiChannelTimer", (cfg.wifichancycle > 0) ? pdMS_TO_TICKS(cfg.wifichancycle) : pdMS_TO_TICKS(50), pdTRUE, (void *)0, switchWifiChannel); + // start timer if (cfg.wifichancycle > 0) - xTimerStart(WifiChanTimer, (TickType_t) 0); - else - esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); + xTimerStart(WifiChanTimer, (TickType_t)0); } void switch_wifi_sniffer(uint8_t state) { if (state) { // switch wifi sniffer on ESP_ERROR_CHECK(esp_wifi_start()); - if (cfg.wifichancycle > 0) - xTimerStart(WifiChanTimer, (TickType_t) 0); - else - esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); + if (cfg.wifichancycle > 0) + xTimerStart(WifiChanTimer, (TickType_t)0); } else { // switch wifi sniffer off + macs_wifi = 0; // clear WIFI counter if (xTimerIsTimerActive(WifiChanTimer) != pdFALSE) - xTimerStop(WifiChanTimer, (TickType_t) 0); + xTimerStop(WifiChanTimer, (TickType_t)0); esp_wifi_set_promiscuous(false); ESP_ERROR_CHECK(esp_wifi_stop()); - macs_wifi = 0; // clear WIFI counter } } \ No newline at end of file From 8d874beaefb531765ff7d316bdeab69da981e9f0 Mon Sep 17 00:00:00 2001 From: Klaus K Wilting Date: Fri, 11 Dec 2020 18:02:18 +0100 Subject: [PATCH 023/104] new MACFILTER, replacing VENDORFILTER --- README.md | 8 +- include/globals.h | 2 +- include/vendor_array.h | 243 --------------------------------------- src/blecsan.cpp | 10 +- src/configmanager.cpp | 16 +-- src/macsniff.cpp | 13 --- src/main.cpp | 2 +- src/paxcounter_orig.conf | 2 +- src/payload.cpp | 5 +- src/rcommand.cpp | 8 +- src/wifiscan.cpp | 10 +- 11 files changed, 34 insertions(+), 285 deletions(-) delete mode 100644 include/vendor_array.h diff --git a/README.md b/README.md index 658782f5..329310e4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Tutorial (in german language): https://www.heise.de/select/make/2019/1/155109923 # Use case -Paxcounter is a proof-of-concept device for metering passenger flows in realtime. It counts how many mobile devices are around. This gives an estimation how many people are around. Paxcounter detects Wifi and Bluetooth signals in the air, focusing on mobile devices by filtering vendor OUIs in the MAC adress. +Paxcounter is a proof-of-concept device for metering passenger flows in realtime. It counts how many mobile devices are around. This gives an estimation how many people are around. Paxcounter detects Wifi and Bluetooth signals in the air, focusing on mobile devices by filtering their MAC adresses. Intention of this project is to do this without intrusion in privacy: You don't need to track people owned devices, if you just want to count them. Therefore, Paxcounter does not persistenly store MAC adresses and does no kind of fingerprinting the scanned devices. @@ -211,7 +211,7 @@ This describes how to set up a mobile PaxCounter:
Follow all steps so far fo Bluetooth low energy service UUID 0xFD6F, used by Google/Apple COVID-19 Exposure Notification System, can be monitored and counted. By comparing with the total number of observed devices this
gives an indication how many people staying in proximity are using Apps for tracing COVID-19 exposures, e.g. in Germany the "Corona Warn App". To achive best results with this funcion, use following settings in `paxcounter.conf`: #define COUNT_ENS 1 // enable ENS monitoring function - #define VENDORFILTER 0 // disable OUI filter (scans ALL device MACs) + #define MACFILTER 0 // disable MAC filter #define BLECOUNTER 1 // enable bluetooth sniffing #define WIFICOUNTER 0 // disable wifi sniffing (improves BLE scan speed) #define HAS_SENSOR_1 1 // optional: transmit ENS counter data to server @@ -307,7 +307,7 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering. byte 11: Bluetooth channel switch interval in seconds/100 (0..255) [efault 10] byte 12: Bluetooth scanner status (1=on, 0=0ff) [default 1] byte 13: Wifi antenna switch (0=internal, 1=external) [default 0] - byte 14: Vendorfilter mode (0=disabled, 1=enabled) [default 0] + byte 14: count randomizated MACs only (0=disabled, 1=enabled) [default 1] byte 15: RGB LED luminosity (0..100 %) [default 30] byte 16: Payload filter mask byte 17: Beacon proximity alarm mode (1=on, 0=off) [default 0] @@ -448,7 +448,7 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0 ... 255 duration for scanning a bluetooth advertising channel in seconds/100 e.g. 8 -> each channel is scanned for 80 milliseconds [default] -0x0D (NOT YET IMPLEMENTED) set BLE and WIFI vendorfilter mode +0x0D (NOT YET IMPLEMENTED) set BLE and WIFI MAC filter mode 0 = disabled (use to count devices, not people) 1 = enabled [default] diff --git a/include/globals.h b/include/globals.h index 89b9ecca..9c771b50 100644 --- a/include/globals.h +++ b/include/globals.h @@ -87,7 +87,7 @@ typedef struct __attribute__((packed)) { uint8_t blescan; // 0=disabled, 1=enabled uint8_t wifiscan; // 0=disabled, 1=enabled uint8_t wifiant; // 0=internal, 1=external (for LoPy/LoPy4) - uint8_t vendorfilter; // 0=disabled, 1=enabled + uint8_t macfilter; // 0=disabled, 1=enabled uint8_t rgblum; // RGB Led luminosity (0..100%) uint8_t monitormode; // 0=disabled, 1=enabled uint8_t runmode; // 0=normal, 1=update diff --git a/include/vendor_array.h b/include/vendor_array.h deleted file mode 100644 index b3bc7744..00000000 --- a/include/vendor_array.h +++ /dev/null @@ -1,243 +0,0 @@ -std::array vendors = { - 0xa44519, 0x2446c8, 0xd0d003, 0x8cb84a, 0x608b0e, 0x88299c, 0x7c8956, - 0x201742, 0xd09c7a, 0xc4e39f, 0x2479f3, 0x8c79f5, 0xccd281, 0x88b291, - 0xc42ad0, 0xd81edd, 0xacf6f7, 0xc02e25, 0x64c2de, 0x58c6f0, 0x70bc10, - 0x2ca9f0, 0x586b14, 0xbcb863, 0x1040f3, 0x44e66e, 0xc0e862, 0xf40616, - 0x601d91, 0xd4c94b, 0x70bbe9, 0xf83880, 0x4c569d, 0x38539c, 0x402619, - 0x6ce85c, 0xe4b2fb, 0xd003df, 0xb4cb57, 0xfc039f, 0x18d717, 0x0057c1, - 0xa4d990, 0x20a60c, 0x08c5e1, 0x3c576c, 0xe4b021, 0x28ff3c, 0xf099b6, - 0x1094bb, 0x88e9fe, 0x94bf2d, 0xf86fc1, 0x38892c, 0x749eaf, 0x8035c1, - 0x2047da, 0xe4c483, 0xc048e6, 0x785dc8, 0x40cbc0, 0xc4618b, 0x08e689, - 0xdc56e7, 0x38e60a, 0xdcbfe9, 0xcc6ea4, 0xa46cf1, 0x08aed6, 0xa816d0, - 0x3408bc, 0x1c36bb, 0x3c2eff, 0xf06e0b, 0x24f677, 0xb0ca68, 0xc83c85, - 0x5433cb, 0x149f3c, 0x9ce063, 0xd03169, 0x7c6456, 0x084acf, 0xb8634d, - 0xa4e975, 0x3035ad, 0x844167, 0x9800c6, 0xac1f74, 0xa85c2c, 0x00db70, - 0x4c49e3, 0x389af6, 0xe0aa96, 0x507705, 0xa89675, 0x00ec0a, 0xc8d7b0, - 0x9097f3, 0x7c1c68, 0xc087eb, 0x388c50, 0xd4503f, 0x245ba7, 0x70f087, - 0xf0d7aa, 0x3096fb, 0x4827ea, 0x7c787e, 0x28395e, 0x38295a, 0x8cf5a3, - 0xd47ae2, 0xd8e0e1, 0xe44790, 0xb0702d, 0x6c19c0, 0xf01dbc, 0xf83f51, - 0x404e36, 0xdc0b34, 0x60a4d0, 0x008701, 0x5c9960, 0x2c3361, 0x101dc0, - 0x78471d, 0xa07591, 0x0cdfa4, 0x68ebae, 0x444e1a, 0x4844f7, 0x001377, - 0x002454, 0xe81132, 0x50b7c3, 0x1c5a3e, 0xa02195, 0xf4d9fb, 0x3c6200, - 0x00e3b2, 0x301966, 0x94350a, 0x0017d5, 0x001e7d, 0x001df6, 0xc06599, - 0xbc79ad, 0xf0e77e, 0xf008f1, 0xe47cf9, 0x58c38b, 0x001d25, 0x08fd0e, - 0x041bba, 0x889b39, 0xe432cb, 0x10d542, 0xa0821f, 0xf06bca, 0xac3613, - 0xbc8ccd, 0xd022be, 0xec9bf3, 0xf409d8, 0x4c3c16, 0x0073e0, 0x343111, - 0x4849c7, 0x849866, 0x9476b7, 0xc4438f, 0xa09169, 0x9893cc, 0x3ccd93, - 0x2021a5, 0x6cd68a, 0x002483, 0x001fe3, 0x2c54cf, 0x485929, 0x58a2b5, - 0x10f96f, 0x4c6641, 0xac3743, 0x28e31f, 0xecadb8, 0x9801a7, 0xb8bbaf, - 0x60c5ad, 0x24f094, 0x086d41, 0xe4faed, 0x288335, 0xdccf96, 0xccb11a, - 0xac61ea, 0x38b54d, 0x1c5cf2, 0xe0c767, 0x80ed2c, 0xa80600, 0xf05a09, - 0x503275, 0x08fc88, 0xf8d0bd, 0x94b10a, 0x3cbbfd, 0xa48431, 0xa0b4a5, - 0xe4f8ef, 0x78595e, 0x0c1420, 0x24f5aa, 0x988389, 0x84a466, 0xc4576e, - 0x508569, 0x489d24, 0xb07994, 0xa470d6, 0xc8f230, 0x8c0ee3, 0x28ed6a, - 0x38f23e, 0x902155, 0xd8b377, 0xa09347, 0xe8bba8, 0xb0aa36, 0xa4d18c, - 0x241eeb, 0xcc25ef, 0x28cfe9, 0x00a040, 0x003065, 0x3cd0f8, 0x680927, - 0x1093e9, 0x442a60, 0xc82a14, 0x3c0754, 0xa4b197, 0xa4d1d2, 0x28cfda, - 0x6cc26b, 0x44d884, 0x64200c, 0x001451, 0x001e52, 0x0021e9, 0x002608, - 0x0026b0, 0x0026bb, 0xd49a20, 0xf81edf, 0xcc08e0, 0xf0b479, 0x045453, - 0xe88d28, 0x949426, 0x207d74, 0xf4f15a, 0xc86f1d, 0x18af8f, 0xc8b5b7, - 0x90b21f, 0xb8e856, 0xd89695, 0x64a3cb, 0x30f7c5, 0x40b395, 0x44fb42, - 0x98fe94, 0xd8004d, 0x542696, 0xc8334b, 0x64e682, 0x9c207b, 0xb065bd, - 0x8c2daa, 0x848506, 0xf4f951, 0xc06394, 0x3090ab, 0x1499e2, 0xfce998, - 0x0cbc9f, 0x34363b, 0xd0a637, 0x789f70, 0xe0accb, 0xa0999b, 0x24240e, - 0x903c92, 0xd81d72, 0xd8bb2c, 0xd04f7e, 0x9cf387, 0xa85b78, 0xb418d1, - 0xf0dbf8, 0x48746e, 0x341298, 0x70e72c, 0x70ece4, 0x54ae27, 0xc8f650, - 0x68ae20, 0xac87a3, 0xa88e24, 0x2078f0, 0x70480f, 0xcc20e8, 0xf0b0e7, - 0x0469f8, 0x205531, 0xe8b4c8, 0xd087e2, 0xf05b7b, 0xb047bf, 0x7c0bc6, - 0x80c5e6, 0x5440ad, 0x804e81, 0xfc8f90, 0xe89120, 0x4480eb, 0x9cd35b, - 0xa89fba, 0x4886e8, 0x30d587, 0xec59e7, 0x2c2997, 0xd059e4, 0x14a364, - 0x7ced8d, 0x002538, 0x0022a1, 0x7cf31b, 0x0c2fb0, 0xf4d620, 0x8cf112, - 0x44aeab, 0xa4f05e, 0x28167f, 0xf887f1, 0x305714, 0xc8b1cd, 0x1460cb, - 0xb8f12a, 0x04c807, 0x20f478, 0x90735a, 0x00fa21, 0x7c2302, 0xd49dc0, - 0xe0dcff, 0x846fce, 0x804a14, 0x703c69, 0x489dd1, 0xb06fe0, 0xc08c71, - 0x60d0a9, 0x48fda3, 0xa45046, 0x007c2d, 0xb831b5, 0x14c213, 0xa4d931, - 0xbcfed9, 0x808223, 0x5029f5, 0x007204, 0xbca58b, 0x80ceb9, 0xe06267, - 0x082525, 0xf460e2, 0x1cc3eb, 0x00c3f4, 0x7836cc, 0x48605f, 0x2816a8, - 0x9c2ea1, 0xbce143, 0x647033, 0x846878, 0xc8d083, 0x6030d4, 0xf895ea, - 0x18f1d8, 0x30d9d9, 0x0cf346, 0x80ad16, 0xfc643a, 0xa8515b, 0xb4f7a1, - 0x88bd45, 0x54fcf0, 0x306a85, 0x4cdd31, 0xd0817a, 0x98ca33, 0x68ab1e, - 0x70ef00, 0xbcffeb, 0x0c1539, 0x88b4a6, 0x2c8a72, 0xd88f76, 0x409c28, - 0xd0b128, 0xbc5451, 0xec51bc, 0xf079e8, 0x58e28f, 0x787b8a, 0x88365f, - 0x2c4053, 0x3cf591, 0x602101, 0xacafb9, 0x041b6d, 0xecf342, 0x503da1, - 0x4c1a3d, 0x503237, 0xb0481a, 0xb49cdf, 0x48bf6b, 0x3c0518, 0x900628, - 0x9c84bf, 0x00b362, 0xe4e4ab, 0x60334b, 0xf4f524, 0xcc2d83, 0xfcd848, - 0xec107b, 0x1c232c, 0x0021d1, 0x001fcc, 0xec8892, 0x60a10a, 0x8c71f8, - 0xcc051b, 0x8c7712, 0x9463d1, 0x0021d2, 0x5c497d, 0x7825ad, 0xece09b, - 0xbc20a4, 0x08d42b, 0x789ed0, 0xb0c4e7, 0xa00798, 0x001fcd, 0x38ece4, - 0x945103, 0x002490, 0x0023d7, 0x549b12, 0xfca13e, 0x24c696, 0x94d771, - 0xe84e84, 0x001632, 0xe4e0c5, 0xc81479, 0x1caf05, 0x0016db, 0x001ee2, - 0x20d5bf, 0x5ce8eb, 0xc0bdd1, 0xb479a7, 0xb0df3a, 0x805719, 0x34be00, - 0x78521a, 0x38256b, 0x205ef7, 0x141f78, 0xb47443, 0x30766f, 0xa8922c, - 0xf80cf3, 0xc49a02, 0x001f6b, 0x0026e2, 0xa860b6, 0xc4b301, 0xe05f45, - 0x483b38, 0x1c9148, 0x5c70a3, 0x64cc2e, 0xf877b8, 0x182195, 0x44783e, - 0x30636b, 0xa4f1e8, 0x14bb6e, 0xe498d1, 0x6c2779, 0x28cc01, 0x6cf373, - 0x9c3aaf, 0x781fdb, 0x4ca56d, 0xb86ce8, 0x0cb319, 0x183f47, 0xb46293, - 0x50a4c8, 0x1867b0, 0x6c8336, 0x002557, 0x001ccc, 0xb4e1c4, 0xe0757d, - 0x34bb26, 0x806c1b, 0x2082c0, 0xdc6dcd, 0x440010, 0x0056cd, 0x00cdfe, - 0xe498d6, 0xf8db7f, 0x64a769, 0xe899c4, 0xbccfcc, 0xf431c3, 0x64a5c3, - 0xbc3aea, 0x7c1dd9, 0xa086c6, 0x9c99a0, 0x584498, 0x002332, 0x00236c, - 0x0023df, 0x002500, 0x0025bc, 0x0019e3, 0x001b63, 0x001ec2, 0x001ff3, - 0x0010fa, 0x0050e4, 0x000d93, 0x7cfadf, 0x78a3e4, 0x148fc6, 0x286ab8, - 0x28e02c, 0xe0b9ba, 0x00c610, 0xb8f6b1, 0x8cfaba, 0x7cd1c3, 0xf0dce2, - 0x24ab81, 0xe0f847, 0x28e7cf, 0xe4ce8f, 0xa82066, 0xbc52b7, 0x5c5948, - 0xc8bcc8, 0xe8040b, 0x145a05, 0x1caba7, 0xc0847a, 0x34159e, 0x58b035, - 0xdc86d8, 0x90b931, 0xd0e140, 0x24a2e1, 0x80ea96, 0x600308, 0x04f13e, - 0x98f0ab, 0x7831c1, 0x783a84, 0x5c8d4e, 0x8863df, 0x881fa1, 0xc8e0eb, - 0x98b8e3, 0x885395, 0x786c1c, 0x4c8d79, 0x1ce62b, 0x0c3021, 0x0c3e9f, - 0xfcfc48, 0x9c293f, 0x087402, 0x94f6a3, 0x98e0d9, 0xcc29f5, 0x285aeb, - 0xf02475, 0x2c1f23, 0x549f13, 0xf0dbe2, 0x748114, 0x18f643, 0xa45e60, - 0xa01828, 0xd0034b, 0x10417f, 0xa8667f, 0xd02598, 0x80be05, 0x24a074, - 0x84788b, 0x587f57, 0x006d52, 0xacee9e, 0xb857d8, 0xb844d9, 0x1c56fe, - 0xac5a14, 0x08eca9, 0xf8cfc5, 0x7840e4, 0xe09971, 0x10d38a, 0x206274, - 0xd48f33, 0x20a99b, 0x6077e2, 0xfc1910, 0x3ca10d, 0x646cb2, 0x680571, - 0x14b484, 0x3059b7, 0xa43d78, 0x00eebd, 0x502e5c, 0x980d2e, 0xd4206d, - 0x7c1e52, 0xdcb4c4, 0x7c6f06, 0x749ef5, 0x68bfc4, 0x04b1a1, 0xcc464e, - 0x507ac5, 0x6cd71f, 0x4c6be8, 0x8c861e, 0x542b8d, 0x8ce5c0, 0xf08a76, - 0xecaa25, 0x9078b2, 0xbc7fa4, 0x687d6b, 0x485169, 0xa8db03, 0x60ab67, - 0x4418fd, 0x005b94, 0xe0897e, 0xa8346a, 0x3c20f6, 0x7c38ad, 0x885a06, - 0x2c5d34, 0x7c035e, 0xd467d3, 0xa41232, 0x64c753, 0x38f9d3, 0xfc183c, - 0xf47def, 0x7c8bb5, 0x3830f9, 0x90e17b, 0xd81c79, 0x58e6ba, 0x1801f1, - 0x9c0cdf, 0x14c697, 0x7c03ab, 0x00b5d0, 0x1496e5, 0xd07fa0, 0x7c6b9c, - 0x108ee0, 0xfca621, 0xd832e3, 0x9487e0, 0x4466fc, 0x50a009, 0x50a67f, - 0xd461da, 0xb841a4, 0x9ce65e, 0xc49880, 0xe0338e, 0xf01898, 0x881908, - 0x5c0947, 0x14205e, 0x08f69c, 0x641cae, 0x003de8, 0x48c796, 0xf4c248, - 0xf47190, 0x2802d8, 0x24181d, 0x641cb0, 0x54b802, 0xd4909c, 0xe4e0a6, - 0x00e091, 0x503cea, 0xb4f1da, 0x80b03d, 0xe49adc, 0xace4b5, 0xf06d78, - 0xd0d2b0, 0x448f17, 0x1cddea, 0x78886d, 0x20ee28, 0xb4f61c, 0x08f4ab, - 0x8c8590, 0x6c96cf, 0x508f4c, 0xd463c6, 0xdc44b6, 0x1007b6, 0x342d0d, - 0x54bd79, 0x30074d, 0x947be7, 0x5092b9, 0xdc74a8, 0x88d50c, 0xc4ae12, - 0x989e63, 0x886b6e, 0xd4dccd, 0x484baa, 0xd4ae05, 0xdca904, 0x6cab31, - 0x4c74bf, 0x5082d5, 0xf0ee10, 0xb81daa, 0x5caf06, 0x64b0a6, 0x7c04d0, - 0x84fcac, 0xdc0c5c, 0x70700d, 0x186590, 0xf86214, 0x784f43, 0x404d7f, - 0x1c48ce, 0x6c5c14, 0xcc61e5, 0x609ac1, 0x20dbab, 0x748d08, 0x9c8ba0, - 0xcc088d, 0x38a4ed, 0x0007ab, 0xe8e5d6, 0xc87e75, 0x00265f, 0x00233a, - 0x382dd1, 0x10ddb1, 0x00166c, 0x001599, 0x0012fb, 0xd0667b, 0x1c66aa, - 0x1489fd, 0xbc851f, 0x002491, 0x3c8bfe, 0xd4e8b2, 0xe8039a, 0x30cda7, - 0x2c4401, 0xb8d9ce, 0x002339, 0x5001bb, 0x001247, 0x0015b9, 0xb85e7b, - 0x1c77f6, 0x742344, 0xc8ba94, 0x843838, 0x54880e, 0xf025b7, 0xe492fb, - 0x6cb7f4, 0x181eb0, 0x5c3c27, 0xbc72b1, 0x78f7be, 0x684898, 0x3423ba, - 0x400e85, 0x88797e, 0xd013fd, 0x888322, 0xe89309, 0x6cd032, 0x3cbdd8, - 0x78c3e9, 0x64899a, 0xf8a9d0, 0xccfa00, 0xa816b2, 0x64bc0c, 0x74a722, - 0xf01c13, 0x344df7, 0x583f54, 0xc01ada, 0x8c1abf, 0x30cbf8, 0xa0cbfd, - 0xe45d75, 0x408805, 0x98398e, 0xd0fccc, 0xc09727, 0x84a134, 0x0c5101, - 0x2cf0a2, 0x68fb7e, 0x6c2483, 0x5cca1a, 0xac0d1b, 0x0034da, 0x202d07, - 0xb44bd2, 0xdc415f, 0x102ab3, 0xe8b2ac, 0xe49a79, 0xf45c89, 0x20768f, - 0x300d43, 0x607edd, 0x08373d, 0xc488e5, 0x1077b1, 0x64b853, 0x389496, - 0x5056bf, 0x90f1aa, 0x74458a, 0xfcc734, 0x8425db, 0xb0ec71, 0xe458b8, - 0x088c2c, 0xa49a58, 0x08ee8b, 0x68ed43, 0x70aab2, 0x000f86, 0xf8e079, - 0xccc3ea, 0x40786a, 0x38cada, 0x34ab37, 0x18af61, 0x5cf938, 0x002376, - 0x38e7d8, 0x188796, 0xb4cef6, 0xcc4463, 0x6c72e7, 0x741bb2, 0xf8a45f, - 0x640980, 0x185936, 0x60fec5, 0xe425e7, 0x7c6d62, 0x40d32d, 0xc42c03, - 0x9027e4, 0xbc926b, 0x101c0c, 0x080007, 0x0016cb, 0x0017f2, 0x001f5b, - 0x002436, 0x00254b, 0x6c3e6d, 0xbc6778, 0x20c9d0, 0x68967b, 0x581faa, - 0x88c663, 0xa46706, 0x8c5877, 0xa8fad8, 0x008865, 0xbc3baf, 0x3ce072, - 0x84fcfe, 0xe48b7f, 0xd8d1cb, 0xb817c2, 0x7c11be, 0x98d6bb, 0x283737, - 0x50ead6, 0x189efc, 0x804971, 0x7cf05f, 0x109add, 0x848e0c, 0x3c15c2, - 0x6c709f, 0x6476ba, 0x34e2fd, 0x04489a, 0x80e650, 0x90fd61, 0x2cf0ee, - 0x5c97f3, 0x8c2937, 0xaccf5c, 0x80006e, 0xa4c361, 0xb09fba, 0x0c4de9, - 0xe0f5c6, 0xa0edcd, 0x087045, 0xa88808, 0xc0f2fb, 0x24e314, 0xf0f61c, - 0x38484c, 0x38c986, 0xd03311, 0xd4f46f, 0x48437c, 0x34a395, 0x787e61, - 0x60f81d, 0x5cf5da, 0x18ee69, 0x649abe, 0xac293a, 0x9cfc01, 0x9c35eb, - 0xf099bf, 0x94e96a, 0x507a55, 0x5882a8, 0x24da9b, 0xe83a12, 0x80656d, - 0x38d40b, 0x209bcd, 0xfcf136, 0x18895b, 0x7cf90e, 0x50f0d3, 0x78bdbc, - 0x84119e, 0xe4907e, 0x149a10, 0x485073, 0x244b03, 0x74e28c, 0x244b81, - 0x60af6d, 0xb85a73, 0x981dfa, 0x102f6b, 0xc44202, 0x103047, 0xf884f2, - 0x5c2e59, 0xe0cbee, 0x002248, 0x8031f0, 0xe0d083, 0xd80b9a, 0x5c666c, - 0xc4e1a1, 0x1855e3, 0xe450eb, 0x886440, 0x6070c0, 0xf0c371, 0x987a14, - 0xc83ddc, 0x845733, 0x1819d6, 0xbc98df, 0x70dda8, 0x4c6f9c, 0x4883b4, - 0x0017fa, 0x941625, 0x34a8eb, 0xa483e7, 0xf4afe7, 0xac88fd, 0x20326c, - 0x6489f1, 0x2034fb, 0xa89ced, 0xe458e7, 0xdc080f, 0xf8e94e, 0xec2ce2, - 0x40bc60, 0xe83617, 0x9c648b, 0x344262, 0x14d00d, 0x703a51, 0x04e598, - 0x6cc7ec, 0xc0bdc8, 0x647bce, 0xa887b3, 0x6c006b, 0x24fce5, 0x9caa1b, - 0x70fd46, 0x8c83e1, 0x889f6f, 0x482ca0, 0x0ccb85, 0x08d46a, 0x68e7c2, - 0x58b10f, 0x645aed, 0xc0b658, 0x48a91c, 0x50bc96, 0xfc2a9c, 0xa056f3, - 0x549963, 0x90dd5d, 0xc819f7, 0x3880df, 0x3cdcbc, 0x804e70, 0xd4e6b7, - 0x04d13a, 0xc48466, 0x347c25, 0xcc2db7, 0x0c9838, 0xd41a3f, 0x5c865c, - 0x04b167, 0xec8350, 0x2c5491, 0xe42b34, 0x3c2ef9, 0xa04ea7, 0xf0989d, - 0xd80831, 0x10f1f2, 0x982d68, 0xb019c6, 0x3866f0, 0x703eac, 0x7c2edd, - 0x3cf7a4, 0x986f60, 0xc49ded, 0x9810e8, 0xc0d012, 0xbca920, 0x48a195, - 0xf80377, 0x8058f8, 0xdca4ca, 0x8c8fe9, 0x5cba37, 0x2c200b, 0x8866a5, - 0xacc1ee, 0x805a04, 0xbc8385, 0x2c598a, 0xe47dbd, 0x001cb3, 0x24920e, - 0xfc4203, 0xa01081, 0xf07960, 0xa0d795, 0xbc4760, 0x04180f, 0x2013e0, - 0x002566, 0xc09f05, 0x606bbd, 0x00214c, 0x0018af, 0x001ee1, 0x00166b, - 0x0000f0, 0x8cc8cd, 0xa8f274, 0xd487d8, 0x184617, 0x380a94, 0xd0dfc7, - 0xd0c1b1, 0x8018a7, 0xf47b5e, 0x70f927, 0xc45006, 0x88329b, 0x1449e0, - 0xd02544, 0xbc4486, 0x20d390, 0x9401c2, 0x50fc9f, 0x380b40, 0xb8ff61, - 0xf0728c, 0x34aa8b, 0x24dbed, 0x68c44d, 0x440444, 0x84b541, 0x006f64, - 0xdc6672, 0xf8e61a, 0xcc2d8c, 0x98d6f7, 0x700514, 0xe892a4, 0x10683f, - 0x40b0fa, 0x0025e5, 0x0021fb, 0x34fcef, 0xbcf5ac, 0x0c4885, 0x0022a9, - 0x9c2a83, 0xa039f7, 0xb0e235, 0x14c913, 0xe09861, 0xa06090, 0xbc765e, - 0xd85b2a, 0x90c1c6, 0x70a2b3, 0xf40f24, 0x4c57ca, 0xc46ab7, 0x48e9f1, - 0xc83f26, 0x40163b, 0xc83870, 0x6c8fb5, 0x1c9e46, 0xc0ccf8, 0x9c4fda, - 0x8489ad, 0x647791, 0x9ce6e7, 0x9c0298, 0x28987b, 0x54fa3e, 0x0c8910, - 0x78abbb, 0xd8c4e9, 0xbcd11f, 0xf4428f, 0x446d6c, 0x00f46f, 0x0c715d, - 0x34bb1f, 0x406f2a, 0x985fd3, 0x60beb5, 0xf8f1b6, 0xf4f1e1, 0x9cd917, - 0x9068c3, 0x68dbca, 0x086698, 0xbc5436, 0x044bed, 0x6c8dc1, 0x0cd746, - 0x60a37d, 0xd40b1a, 0xfc64ba, 0x9060f1, 0xa4516f, 0x68dfdd, 0x98fae3, - 0xf0b429, 0xb8782e, 0x000502, 0x000a95, 0x00264a, 0x041e64, 0x001124, - 0x002241, 0x7cc537, 0x78ca39, 0x18e7f4, 0x70cd60, 0x8c7b9d, 0xd89e3f, - 0xb8c75d, 0x0c74c2, 0x403004, 0x842999, 0x74e2f5, 0xe0c97a, 0x68a86d, - 0x7cc3a1, 0x7073cb, 0x90840d, 0xe80688, 0xec852f, 0x00f4b9, 0x5c95ae, - 0x9803d8, 0x60c547, 0x685b35, 0x2cb43a, 0x689c70, 0x380f4a, 0x3010e4, - 0xa886dd, 0x444c0c, 0xb4f0ab, 0x80929f, 0x9c04eb, 0x5c969d, 0x609217, - 0x84b153, 0xe06678, 0x48d705, 0x041552, 0xcc785f, 0x88cb87, 0xf0c1f1, - 0x843835, 0x8c006d, 0xa8968a, 0xf41ba1, 0x60d9c7, 0x3cab8e, 0xf82793, - 0x907240, 0x908d6c, 0xb8098a, 0x4c7c5f, 0x68644b, 0xc81ee7, 0xa43135, - 0x68d93c, 0x00f76f, 0xc88550, 0x7014a6, 0x985aeb, 0x78d75f, 0xe0b52d, - 0x6c94f8, 0xc0cecd, 0x2cae2b, 0xc869cd, 0xa4b805, 0x5cadcf, 0xbc6c21, - 0xf40e22, 0x544e90, 0xc01173, 0xbce63f, 0x7c9122, 0xacbc32, 0x2827bf, - 0x800184, 0x8cbfa6, 0xc8a823, 0xb0c559, 0xbc1485, 0x9c6c15, 0xc0335e, - 0xd0929e, 0x083d88, 0x608f5c, 0x109266, 0x281878, 0x847a88, 0xa0f450, - 0xa826d9, 0x00155d, 0x00125a, 0x000d3a, 0x0003ff, 0x18d0c5, 0x1cccd6, - 0x582059, 0x74e1b6, 0xf40e01, 0x1495ce, 0x50de06, 0xcc660a, 0xfc1d43, - 0x0024e9, 0xb4c4fc, 0x7cd661, 0x98b8ba, 0x103025, 0xb8b2f8, 0x98460a, - 0xb85d0a, 0x7c9a1d, 0x5462e2, 0x149d99, 0x489507, 0xd85575, 0xd411a3, - 0x04ba8d, 0xe446da, 0x58d9c3, 0x3c8375, 0xb8bc5b, 0xd8ce3a, 0xb8c74a, - 0x94f6d6, 0xf82d7c, 0xc09ad0, 0x94b01f, 0x90633b, 0xfcaab6, 0x782327, - 0xdcf756, 0x74b587, 0xfcb6d8, 0x18810e, 0x608c4a, 0x241b7a, 0x8cfe57, - 0xc0a600, 0xa823fe, 0x1c427d, 0xcc2119, 0x304b07, 0x000393, 0x702ad5, - 0x74eb80, 0x0ce0dc, 0xd868c3, 0xc493d9, 0xa82bb9, 0x1c1ac0, 0x88ae07, - 0x40831d, 0xdcd3a2, 0x5c1dd9, 0x68fef7, 0xd07714, 0x587a6a, 0x705aac, - 0x685acf, 0x0ca8a7, 0x2c61f6, 0xd4a33d, 0xf0766f, 0x4098ad, 0x6c4d73, - 0x68ef43, 0xd02b20, 0xd86375, 0x30b4b8, 0x9c8c6e, 0x18f0e4, 0x00bf61, - 0xd00401, 0xdc5583, 0xb8c111, 0x9ce33f, 0x7867d7, 0x087808, 0x887598, - 0xc0174d, 0xa407b6, 0x04d6aa, 0x08152f, 0xf4f5db, 0xc0a53e, 0xecd09f, - 0xa8be27, 0x94d029, 0x308454, 0x5c5181, 0x608e08, 0x4c189a, 0x58c5cb, - 0xb4bff6, 0x74f61c, 0x84c0ef, 0xa8b86e, 0xc40bcb, 0xb83765, 0x14bd61, - 0xc0d3c0, 0x948bc1, 0x14568e, 0xd4619d, 0x7c5049, 0x64b473, 0x7451ba, - 0x7802f8, 0xec01ee, 0x682737, 0x58404e, 0xd0c5f3, 0xbc9fef, 0x20ab37, - 0x60f445, 0x2c0e3d, 0x88e87f, 0x9cf48e, 0x5cf7e6, 0xb853ac, 0x203cae, - 0x2cbaba, 0x40d3ae, 0xa03be3, 0x4c3275, 0x3816d1, 0xd0176a, 0xd48890, - 0x5492be, 0x949aa9, 0x001c43, 0x002399, 0x0017c9, 0x7cf854, 0xc4731e, - 0xec1f72, 0xe4121d, 0xe8508b, 0xf8042e, 0x0023d6, 0x001b98, 0x001a8a, - 0x3c5a37, 0xf49f54, 0x34c3ac, 0x44f459, 0x00265d, 0x002567, 0xbcb1f3, - 0xc462ea, 0x182666, 0x30d6c9, 0xcc07ab, 0x1c62b8, 0xccf9e8, 0xd857ef, - 0x18e2c2, 0x28bab5, 0xe440e2, 0x103b59, 0xd890e8, 0x9852b1, 0x14f42a, - 0x0808c2, 0xccfe3c, 0xb43a28, 0x78a873, 0xd83062, 0xc041f6, 0x001e75, - 0x001c62, 0xf895c7, 0x34145f, 0x0821ef, 0x505527, 0x88c9d0, 0x8c3ae3, - 0x00aa70, 0x60e3ac, 0x4c0bbe, 0xb88d12, 0x845181, 0x348a7b, 0x78009e, - 0xacc33a, 0x54f201, 0x70288b, 0x8c8ef2, 0x90b0ed, 0x04d3cf, 0x1c3ade, - 0x509ea7, 0xa88195, 0x88add2, 0xac5f3e, 0xb48b19, 0xbcec5d, 0x28a02b, - 0xb8c68e, 0x04fe31, 0x4cbca5, 0xd831cf, 0x188331, 0x9c65b0, 0x8455a5, - 0xa87c01, 0x50f520, 0x64b310, 0xa4ebd3, 0xb0d09c, 0x50c8e5, 0x5cf6dc, - 0x0026ff, 0xa4e4b8, 0xf40b93, 0x1c69a5, 0x94ebcd, 0x141aa3, 0x1430c6, - 0x88074b, 0x3871de, 0x7081eb, 0x78f882, 0x606944, 0x807abf, 0x00092d, - 0x7c6193, 0x0452f3, 0x90e7c4, 0xa81b5a, 0x2c5bb8, 0xacf7f3, 0xd4970b, - 0x8cbebe, 0x14f65a, 0x009ec8, 0x0c1daf, 0x3480b3, 0xf48b32, 0x60fb42, - 0x64b9e8, 0xd8a25e, 0x000a27, 0x001d4f, 0x002312, 0x60facd, 0x003ee1, - 0xfc253f, 0x183451, 0x0c771a, 0x286aba, 0x3451c9, 0x406c8f, 0xd023db, - 0x70dee2, 0x5855ca, 0xdc2b61, 0x40a6d9, 0x4cb199, 0xc09f42, 0x705681, - 0x040cce, 0x403cfc, 0x4860bc, 0xf0cba1, 0x182032, 0x34c059, 0xf0d1a9, - 0x14109f, 0x04f7e4, 0xdc9b9c, 0x54724f, 0x8c7c92, 0xb03495, 0x042665, - 0xec3586, 0x54eaa8, 0x28e14c, 0xe4c63d, 0x54e43a, 0x04db56, 0x04e536, - 0xe8802e, 0x006171, 0x6c4008, 0x7c6df8, 0x78fd94, 0x2cbe08, 0xac3c0b, - 0x701124, 0xf437b7, 0xd8cf9c, 0xa8bbcf, 0xac7f3e, 0x280b5c, 0xacfdec, - 0x28f076, 0x20a2e4, 0xbc4cc4, 0xdc3714, 0x40331a, 0xccc760, 0x7c0191, - 0x84100d, 0x80d605, 0xdc2b2a, 0x48137e, 0x382de8, 0xc08997, 0x04c23e, - 0x380195, 0xb4ae2b, 0x5c5188, 0x0ce725, 0xe0db10, 0x1432d1, 0x1816c9, - 0x842e27, 0x0c413e, 0x9000db, 0xb4ef39, 0xc808e9, 0x183a2d, 0x8463d6, - 0xb84fd5, 0xc0eefb, 0x206e9c, 0x6c2f2c, 0x18227e, 0x30c7ae, 0x501ac5, - 0x6045bd, 0x1cb094, 0xe85b5b, 0x0025ae, 0x001dd8, 0x000a75, 0xa4c939, - 0xc0dcda, 0x04b429, 0x48794d, -}; \ No newline at end of file diff --git a/src/blecsan.cpp b/src/blecsan.cpp index f39a2e58..a6a0d999 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -151,7 +151,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, ESP_LOGV(TAG, "RSSI : %d", p->scan_rst.rssi); #endif -#if (VENDORFILTER) +#if (MACFILTER) if ((p->scan_rst.ble_addr_type == BLE_ADDR_TYPE_RANDOM) || (p->scan_rst.ble_addr_type == BLE_ADDR_TYPE_RPA_RANDOM)) { #ifdef VERBOSE @@ -176,7 +176,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, mac_add((uint8_t *)p->scan_rst.bda, p->scan_rst.rssi, MAC_SNIFF_BLE); #endif - /* to be improved in vendorfilter if: + /* to be improved in macfilter: // you can search for elements in the payload using the // function esp_ble_resolve_adv_data() // @@ -187,7 +187,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, ESP_BLE_AD_TYPE_NAME_CMPL, &len); filter BLE devices using their advertisements to get filter alternative - to vendor OUI if vendorfiltering is on, we ... + to vendor OUI if macfiltering is on, we ... - want to count: mobile phones and tablets - don't want to count: beacons, peripherals (earphones, headsets, printers), cars and machines see @@ -225,11 +225,11 @@ esp_err_t register_ble_callback(void) { .scan_type = BLE_SCAN_TYPE_PASSIVE, .own_addr_type = BLE_ADDR_TYPE_RANDOM, -#if (VENDORFILTER) +#if (MACFILTER) .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for broadcasting // data in broadcast applications (e.g., Beacons), so we don't want them in - // vendorfilter mode + // macfilter mode #else .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, #endif diff --git a/src/configmanager.cpp b/src/configmanager.cpp index a489801e..8a9da4ce 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -48,14 +48,14 @@ static void defaultConfig(configData_t *myconfig) { myconfig->blescantime = BLESCANINTERVAL / 10; // BT channel scan cycle [seconds/100], default 1 (= 10ms) - myconfig->blescan = 1; // 0=disabled, 1=enabled - myconfig->wifiscan = 1; // 0=disabled, 1=enabled - myconfig->wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4) - myconfig->vendorfilter = VENDORFILTER; // 0=disabled, 1=enabled - myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) - myconfig->monitormode = 0; // 0=disabled, 1=enabled - myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default - myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled + myconfig->blescan = 1; // 0=disabled, 1=enabled + myconfig->wifiscan = 1; // 0=disabled, 1=enabled + myconfig->wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4) + myconfig->macfilter = MACFILTER; // 0=disabled, 1=enabled + myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) + myconfig->monitormode = 0; // 0=disabled, 1=enabled + myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default + myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled #ifdef HAS_BME680 // initial BSEC state for BME680 sensor diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 7d0e4bea..2f5446f2 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -3,10 +3,6 @@ #include "globals.h" #include "macsniff.h" -#if (VENDORFILTER) -#include "vendor_array.h" -#endif - // Local logging tag static const char TAG[] = __FILE__; @@ -130,15 +126,6 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { } }; -#if (VENDORFILTER) - uint32_t *oui; // temporary buffer for vendor OUI - oui = (uint32_t *)MacBuffer.mac; - // if we find OUI on vendor filter list we don't analyze and return early - if (std::find(vendors.begin(), vendors.end(), __builtin_bswap32(*oui) >> 8) == - vendors.end()) - return 0; -#endif - char buff[10]; // temporary buffer for printf uint32_t *mac; // temporary buffer for shortened MAC diff --git a/src/main.cpp b/src/main.cpp index 6bba631a..b567454a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -374,7 +374,7 @@ void setup() { strcat_P(features, " SDS"); #endif -#if (VENDORFILTER) +#if (MACFILTER) strcat_P(features, " FILTER"); #endif diff --git a/src/paxcounter_orig.conf b/src/paxcounter_orig.conf index c0dc5816..4c1a7225 100644 --- a/src/paxcounter_orig.conf +++ b/src/paxcounter_orig.conf @@ -16,7 +16,7 @@ #define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed // MAC sniffing parameters -#define VENDORFILTER 0 // set to 0 if you want to scan all devices, not filtering smartphone OUIs +#define MACFILTER 1 // set to 0 if you want to scan all devices, 1 to scan only devices with random MACs (aka smartphones) [default = 1] #define BLECOUNTER 1 // set to 0 if you do not want to install the BLE sniffer #define WIFICOUNTER 1 // set to 0 if you do not want to install the WIFI sniffer #define MAC_QUEUE_SIZE 50 // size of MAC processing buffer (number of MACs) [default = 50] diff --git a/src/payload.cpp b/src/payload.cpp index 112a544d..904e454c 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -49,7 +49,7 @@ void PayloadConvert::addConfig(configData_t value) { buffer[cursor++] = value.blescantime; buffer[cursor++] = value.blescan; buffer[cursor++] = value.wifiant; - buffer[cursor++] = value.vendorfilter; + buffer[cursor++] = value.macfilter; buffer[cursor++] = value.rgblum; buffer[cursor++] = value.payloadmask; buffer[cursor++] = value.monitormode; @@ -179,8 +179,7 @@ void PayloadConvert::addConfig(configData_t value) { writeBitmap(value.adrmode ? true : false, value.screensaver ? true : false, value.screenon ? true : false, value.countermode ? true : false, value.blescan ? true : false, value.wifiant ? true : false, - value.vendorfilter ? true : false, - value.monitormode ? true : false); + value.macfilter ? true : false, value.monitormode ? true : false); writeUint8(value.payloadmask); writeVersion(value.version); } diff --git a/src/rcommand.cpp b/src/rcommand.cpp index c18db679..a361100e 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -259,10 +259,10 @@ void set_wifiant(uint8_t val[]) { #endif } -void set_vendorfilter(uint8_t val[]) { - ESP_LOGI(TAG, "Remote command: set vendorfilter mode to %s", +void set_macfilter(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: set macfilter mode to %s", val[0] ? "on" : "off"); - cfg.vendorfilter = val[0] ? 1 : 0; + cfg.macfilter = val[0] ? 1 : 0; } void set_rgblum(uint8_t val[]) { @@ -378,7 +378,7 @@ static const cmd_t table[] = { {0x07, set_loraadr, 1, true}, {0x08, set_screensaver, 1, true}, {0x09, set_reset, 1, false}, {0x0a, set_sendcycle, 1, true}, {0x0b, set_wifichancycle, 1, true}, {0x0c, set_blescantime, 1, true}, - {0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true}, + {0x0d, set_macfilter, 1, false}, {0x0e, set_blescan, 1, true}, {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, {0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true}, diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index ccb6a87e..4b85f1ec 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -35,8 +35,14 @@ IRAM_ATTR void wifi_sniffer_packet_handler(void *buff, (wifi_ieee80211_packet_t *)ppkt->payload; const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr; - // process seen MAC - mac_add((uint8_t *)hdr->addr2, ppkt->rx_ctrl.rssi, MAC_SNIFF_WIFI); +// process seen MAC +#if MACFILTER + // we guess it's a smartphone, if randomization bit of MAC is set + if (!(hdr->addr2[0] & 0b10)) + return; + else +#endif + mac_add((uint8_t *)hdr->addr2, ppkt->rx_ctrl.rssi, MAC_SNIFF_WIFI); } // Software-timer driven Wifi channel rotation callback function From 14c8eeb4717dd2a27478fc6f0177542bd5ff9f51 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 13 Dec 2020 19:29:15 +0100 Subject: [PATCH 024/104] new MACFILTER, replacing VENDORFILTER --- README.md | 8 +- include/globals.h | 2 +- include/vendor_array.h | 243 --------------------------------------- src/blecsan.cpp | 10 +- src/configmanager.cpp | 16 +-- src/macsniff.cpp | 13 --- src/main.cpp | 2 +- src/paxcounter_orig.conf | 2 +- src/payload.cpp | 5 +- src/rcommand.cpp | 14 +-- src/wifiscan.cpp | 10 +- 11 files changed, 37 insertions(+), 288 deletions(-) delete mode 100644 include/vendor_array.h diff --git a/README.md b/README.md index 658782f5..329310e4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Tutorial (in german language): https://www.heise.de/select/make/2019/1/155109923 # Use case -Paxcounter is a proof-of-concept device for metering passenger flows in realtime. It counts how many mobile devices are around. This gives an estimation how many people are around. Paxcounter detects Wifi and Bluetooth signals in the air, focusing on mobile devices by filtering vendor OUIs in the MAC adress. +Paxcounter is a proof-of-concept device for metering passenger flows in realtime. It counts how many mobile devices are around. This gives an estimation how many people are around. Paxcounter detects Wifi and Bluetooth signals in the air, focusing on mobile devices by filtering their MAC adresses. Intention of this project is to do this without intrusion in privacy: You don't need to track people owned devices, if you just want to count them. Therefore, Paxcounter does not persistenly store MAC adresses and does no kind of fingerprinting the scanned devices. @@ -211,7 +211,7 @@ This describes how to set up a mobile PaxCounter:
Follow all steps so far fo Bluetooth low energy service UUID 0xFD6F, used by Google/Apple COVID-19 Exposure Notification System, can be monitored and counted. By comparing with the total number of observed devices this gives an indication how many people staying in proximity are using Apps for tracing COVID-19 exposures, e.g. in Germany the "Corona Warn App". To achive best results with this funcion, use following settings in `paxcounter.conf`: #define COUNT_ENS 1 // enable ENS monitoring function - #define VENDORFILTER 0 // disable OUI filter (scans ALL device MACs) + #define MACFILTER 0 // disable MAC filter #define BLECOUNTER 1 // enable bluetooth sniffing #define WIFICOUNTER 0 // disable wifi sniffing (improves BLE scan speed) #define HAS_SENSOR_1 1 // optional: transmit ENS counter data to server @@ -307,7 +307,7 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering. byte 11: Bluetooth channel switch interval in seconds/100 (0..255) [efault 10] byte 12: Bluetooth scanner status (1=on, 0=0ff) [default 1] byte 13: Wifi antenna switch (0=internal, 1=external) [default 0] - byte 14: Vendorfilter mode (0=disabled, 1=enabled) [default 0] + byte 14: count randomizated MACs only (0=disabled, 1=enabled) [default 1] byte 15: RGB LED luminosity (0..100 %) [default 30] byte 16: Payload filter mask byte 17: Beacon proximity alarm mode (1=on, 0=off) [default 0] @@ -448,7 +448,7 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0 ... 255 duration for scanning a bluetooth advertising channel in seconds/100 e.g. 8 -> each channel is scanned for 80 milliseconds [default] -0x0D (NOT YET IMPLEMENTED) set BLE and WIFI vendorfilter mode +0x0D (NOT YET IMPLEMENTED) set BLE and WIFI MAC filter mode 0 = disabled (use to count devices, not people) 1 = enabled [default] diff --git a/include/globals.h b/include/globals.h index 89b9ecca..9c771b50 100644 --- a/include/globals.h +++ b/include/globals.h @@ -87,7 +87,7 @@ typedef struct __attribute__((packed)) { uint8_t blescan; // 0=disabled, 1=enabled uint8_t wifiscan; // 0=disabled, 1=enabled uint8_t wifiant; // 0=internal, 1=external (for LoPy/LoPy4) - uint8_t vendorfilter; // 0=disabled, 1=enabled + uint8_t macfilter; // 0=disabled, 1=enabled uint8_t rgblum; // RGB Led luminosity (0..100%) uint8_t monitormode; // 0=disabled, 1=enabled uint8_t runmode; // 0=normal, 1=update diff --git a/include/vendor_array.h b/include/vendor_array.h deleted file mode 100644 index b3bc7744..00000000 --- a/include/vendor_array.h +++ /dev/null @@ -1,243 +0,0 @@ -std::array vendors = { - 0xa44519, 0x2446c8, 0xd0d003, 0x8cb84a, 0x608b0e, 0x88299c, 0x7c8956, - 0x201742, 0xd09c7a, 0xc4e39f, 0x2479f3, 0x8c79f5, 0xccd281, 0x88b291, - 0xc42ad0, 0xd81edd, 0xacf6f7, 0xc02e25, 0x64c2de, 0x58c6f0, 0x70bc10, - 0x2ca9f0, 0x586b14, 0xbcb863, 0x1040f3, 0x44e66e, 0xc0e862, 0xf40616, - 0x601d91, 0xd4c94b, 0x70bbe9, 0xf83880, 0x4c569d, 0x38539c, 0x402619, - 0x6ce85c, 0xe4b2fb, 0xd003df, 0xb4cb57, 0xfc039f, 0x18d717, 0x0057c1, - 0xa4d990, 0x20a60c, 0x08c5e1, 0x3c576c, 0xe4b021, 0x28ff3c, 0xf099b6, - 0x1094bb, 0x88e9fe, 0x94bf2d, 0xf86fc1, 0x38892c, 0x749eaf, 0x8035c1, - 0x2047da, 0xe4c483, 0xc048e6, 0x785dc8, 0x40cbc0, 0xc4618b, 0x08e689, - 0xdc56e7, 0x38e60a, 0xdcbfe9, 0xcc6ea4, 0xa46cf1, 0x08aed6, 0xa816d0, - 0x3408bc, 0x1c36bb, 0x3c2eff, 0xf06e0b, 0x24f677, 0xb0ca68, 0xc83c85, - 0x5433cb, 0x149f3c, 0x9ce063, 0xd03169, 0x7c6456, 0x084acf, 0xb8634d, - 0xa4e975, 0x3035ad, 0x844167, 0x9800c6, 0xac1f74, 0xa85c2c, 0x00db70, - 0x4c49e3, 0x389af6, 0xe0aa96, 0x507705, 0xa89675, 0x00ec0a, 0xc8d7b0, - 0x9097f3, 0x7c1c68, 0xc087eb, 0x388c50, 0xd4503f, 0x245ba7, 0x70f087, - 0xf0d7aa, 0x3096fb, 0x4827ea, 0x7c787e, 0x28395e, 0x38295a, 0x8cf5a3, - 0xd47ae2, 0xd8e0e1, 0xe44790, 0xb0702d, 0x6c19c0, 0xf01dbc, 0xf83f51, - 0x404e36, 0xdc0b34, 0x60a4d0, 0x008701, 0x5c9960, 0x2c3361, 0x101dc0, - 0x78471d, 0xa07591, 0x0cdfa4, 0x68ebae, 0x444e1a, 0x4844f7, 0x001377, - 0x002454, 0xe81132, 0x50b7c3, 0x1c5a3e, 0xa02195, 0xf4d9fb, 0x3c6200, - 0x00e3b2, 0x301966, 0x94350a, 0x0017d5, 0x001e7d, 0x001df6, 0xc06599, - 0xbc79ad, 0xf0e77e, 0xf008f1, 0xe47cf9, 0x58c38b, 0x001d25, 0x08fd0e, - 0x041bba, 0x889b39, 0xe432cb, 0x10d542, 0xa0821f, 0xf06bca, 0xac3613, - 0xbc8ccd, 0xd022be, 0xec9bf3, 0xf409d8, 0x4c3c16, 0x0073e0, 0x343111, - 0x4849c7, 0x849866, 0x9476b7, 0xc4438f, 0xa09169, 0x9893cc, 0x3ccd93, - 0x2021a5, 0x6cd68a, 0x002483, 0x001fe3, 0x2c54cf, 0x485929, 0x58a2b5, - 0x10f96f, 0x4c6641, 0xac3743, 0x28e31f, 0xecadb8, 0x9801a7, 0xb8bbaf, - 0x60c5ad, 0x24f094, 0x086d41, 0xe4faed, 0x288335, 0xdccf96, 0xccb11a, - 0xac61ea, 0x38b54d, 0x1c5cf2, 0xe0c767, 0x80ed2c, 0xa80600, 0xf05a09, - 0x503275, 0x08fc88, 0xf8d0bd, 0x94b10a, 0x3cbbfd, 0xa48431, 0xa0b4a5, - 0xe4f8ef, 0x78595e, 0x0c1420, 0x24f5aa, 0x988389, 0x84a466, 0xc4576e, - 0x508569, 0x489d24, 0xb07994, 0xa470d6, 0xc8f230, 0x8c0ee3, 0x28ed6a, - 0x38f23e, 0x902155, 0xd8b377, 0xa09347, 0xe8bba8, 0xb0aa36, 0xa4d18c, - 0x241eeb, 0xcc25ef, 0x28cfe9, 0x00a040, 0x003065, 0x3cd0f8, 0x680927, - 0x1093e9, 0x442a60, 0xc82a14, 0x3c0754, 0xa4b197, 0xa4d1d2, 0x28cfda, - 0x6cc26b, 0x44d884, 0x64200c, 0x001451, 0x001e52, 0x0021e9, 0x002608, - 0x0026b0, 0x0026bb, 0xd49a20, 0xf81edf, 0xcc08e0, 0xf0b479, 0x045453, - 0xe88d28, 0x949426, 0x207d74, 0xf4f15a, 0xc86f1d, 0x18af8f, 0xc8b5b7, - 0x90b21f, 0xb8e856, 0xd89695, 0x64a3cb, 0x30f7c5, 0x40b395, 0x44fb42, - 0x98fe94, 0xd8004d, 0x542696, 0xc8334b, 0x64e682, 0x9c207b, 0xb065bd, - 0x8c2daa, 0x848506, 0xf4f951, 0xc06394, 0x3090ab, 0x1499e2, 0xfce998, - 0x0cbc9f, 0x34363b, 0xd0a637, 0x789f70, 0xe0accb, 0xa0999b, 0x24240e, - 0x903c92, 0xd81d72, 0xd8bb2c, 0xd04f7e, 0x9cf387, 0xa85b78, 0xb418d1, - 0xf0dbf8, 0x48746e, 0x341298, 0x70e72c, 0x70ece4, 0x54ae27, 0xc8f650, - 0x68ae20, 0xac87a3, 0xa88e24, 0x2078f0, 0x70480f, 0xcc20e8, 0xf0b0e7, - 0x0469f8, 0x205531, 0xe8b4c8, 0xd087e2, 0xf05b7b, 0xb047bf, 0x7c0bc6, - 0x80c5e6, 0x5440ad, 0x804e81, 0xfc8f90, 0xe89120, 0x4480eb, 0x9cd35b, - 0xa89fba, 0x4886e8, 0x30d587, 0xec59e7, 0x2c2997, 0xd059e4, 0x14a364, - 0x7ced8d, 0x002538, 0x0022a1, 0x7cf31b, 0x0c2fb0, 0xf4d620, 0x8cf112, - 0x44aeab, 0xa4f05e, 0x28167f, 0xf887f1, 0x305714, 0xc8b1cd, 0x1460cb, - 0xb8f12a, 0x04c807, 0x20f478, 0x90735a, 0x00fa21, 0x7c2302, 0xd49dc0, - 0xe0dcff, 0x846fce, 0x804a14, 0x703c69, 0x489dd1, 0xb06fe0, 0xc08c71, - 0x60d0a9, 0x48fda3, 0xa45046, 0x007c2d, 0xb831b5, 0x14c213, 0xa4d931, - 0xbcfed9, 0x808223, 0x5029f5, 0x007204, 0xbca58b, 0x80ceb9, 0xe06267, - 0x082525, 0xf460e2, 0x1cc3eb, 0x00c3f4, 0x7836cc, 0x48605f, 0x2816a8, - 0x9c2ea1, 0xbce143, 0x647033, 0x846878, 0xc8d083, 0x6030d4, 0xf895ea, - 0x18f1d8, 0x30d9d9, 0x0cf346, 0x80ad16, 0xfc643a, 0xa8515b, 0xb4f7a1, - 0x88bd45, 0x54fcf0, 0x306a85, 0x4cdd31, 0xd0817a, 0x98ca33, 0x68ab1e, - 0x70ef00, 0xbcffeb, 0x0c1539, 0x88b4a6, 0x2c8a72, 0xd88f76, 0x409c28, - 0xd0b128, 0xbc5451, 0xec51bc, 0xf079e8, 0x58e28f, 0x787b8a, 0x88365f, - 0x2c4053, 0x3cf591, 0x602101, 0xacafb9, 0x041b6d, 0xecf342, 0x503da1, - 0x4c1a3d, 0x503237, 0xb0481a, 0xb49cdf, 0x48bf6b, 0x3c0518, 0x900628, - 0x9c84bf, 0x00b362, 0xe4e4ab, 0x60334b, 0xf4f524, 0xcc2d83, 0xfcd848, - 0xec107b, 0x1c232c, 0x0021d1, 0x001fcc, 0xec8892, 0x60a10a, 0x8c71f8, - 0xcc051b, 0x8c7712, 0x9463d1, 0x0021d2, 0x5c497d, 0x7825ad, 0xece09b, - 0xbc20a4, 0x08d42b, 0x789ed0, 0xb0c4e7, 0xa00798, 0x001fcd, 0x38ece4, - 0x945103, 0x002490, 0x0023d7, 0x549b12, 0xfca13e, 0x24c696, 0x94d771, - 0xe84e84, 0x001632, 0xe4e0c5, 0xc81479, 0x1caf05, 0x0016db, 0x001ee2, - 0x20d5bf, 0x5ce8eb, 0xc0bdd1, 0xb479a7, 0xb0df3a, 0x805719, 0x34be00, - 0x78521a, 0x38256b, 0x205ef7, 0x141f78, 0xb47443, 0x30766f, 0xa8922c, - 0xf80cf3, 0xc49a02, 0x001f6b, 0x0026e2, 0xa860b6, 0xc4b301, 0xe05f45, - 0x483b38, 0x1c9148, 0x5c70a3, 0x64cc2e, 0xf877b8, 0x182195, 0x44783e, - 0x30636b, 0xa4f1e8, 0x14bb6e, 0xe498d1, 0x6c2779, 0x28cc01, 0x6cf373, - 0x9c3aaf, 0x781fdb, 0x4ca56d, 0xb86ce8, 0x0cb319, 0x183f47, 0xb46293, - 0x50a4c8, 0x1867b0, 0x6c8336, 0x002557, 0x001ccc, 0xb4e1c4, 0xe0757d, - 0x34bb26, 0x806c1b, 0x2082c0, 0xdc6dcd, 0x440010, 0x0056cd, 0x00cdfe, - 0xe498d6, 0xf8db7f, 0x64a769, 0xe899c4, 0xbccfcc, 0xf431c3, 0x64a5c3, - 0xbc3aea, 0x7c1dd9, 0xa086c6, 0x9c99a0, 0x584498, 0x002332, 0x00236c, - 0x0023df, 0x002500, 0x0025bc, 0x0019e3, 0x001b63, 0x001ec2, 0x001ff3, - 0x0010fa, 0x0050e4, 0x000d93, 0x7cfadf, 0x78a3e4, 0x148fc6, 0x286ab8, - 0x28e02c, 0xe0b9ba, 0x00c610, 0xb8f6b1, 0x8cfaba, 0x7cd1c3, 0xf0dce2, - 0x24ab81, 0xe0f847, 0x28e7cf, 0xe4ce8f, 0xa82066, 0xbc52b7, 0x5c5948, - 0xc8bcc8, 0xe8040b, 0x145a05, 0x1caba7, 0xc0847a, 0x34159e, 0x58b035, - 0xdc86d8, 0x90b931, 0xd0e140, 0x24a2e1, 0x80ea96, 0x600308, 0x04f13e, - 0x98f0ab, 0x7831c1, 0x783a84, 0x5c8d4e, 0x8863df, 0x881fa1, 0xc8e0eb, - 0x98b8e3, 0x885395, 0x786c1c, 0x4c8d79, 0x1ce62b, 0x0c3021, 0x0c3e9f, - 0xfcfc48, 0x9c293f, 0x087402, 0x94f6a3, 0x98e0d9, 0xcc29f5, 0x285aeb, - 0xf02475, 0x2c1f23, 0x549f13, 0xf0dbe2, 0x748114, 0x18f643, 0xa45e60, - 0xa01828, 0xd0034b, 0x10417f, 0xa8667f, 0xd02598, 0x80be05, 0x24a074, - 0x84788b, 0x587f57, 0x006d52, 0xacee9e, 0xb857d8, 0xb844d9, 0x1c56fe, - 0xac5a14, 0x08eca9, 0xf8cfc5, 0x7840e4, 0xe09971, 0x10d38a, 0x206274, - 0xd48f33, 0x20a99b, 0x6077e2, 0xfc1910, 0x3ca10d, 0x646cb2, 0x680571, - 0x14b484, 0x3059b7, 0xa43d78, 0x00eebd, 0x502e5c, 0x980d2e, 0xd4206d, - 0x7c1e52, 0xdcb4c4, 0x7c6f06, 0x749ef5, 0x68bfc4, 0x04b1a1, 0xcc464e, - 0x507ac5, 0x6cd71f, 0x4c6be8, 0x8c861e, 0x542b8d, 0x8ce5c0, 0xf08a76, - 0xecaa25, 0x9078b2, 0xbc7fa4, 0x687d6b, 0x485169, 0xa8db03, 0x60ab67, - 0x4418fd, 0x005b94, 0xe0897e, 0xa8346a, 0x3c20f6, 0x7c38ad, 0x885a06, - 0x2c5d34, 0x7c035e, 0xd467d3, 0xa41232, 0x64c753, 0x38f9d3, 0xfc183c, - 0xf47def, 0x7c8bb5, 0x3830f9, 0x90e17b, 0xd81c79, 0x58e6ba, 0x1801f1, - 0x9c0cdf, 0x14c697, 0x7c03ab, 0x00b5d0, 0x1496e5, 0xd07fa0, 0x7c6b9c, - 0x108ee0, 0xfca621, 0xd832e3, 0x9487e0, 0x4466fc, 0x50a009, 0x50a67f, - 0xd461da, 0xb841a4, 0x9ce65e, 0xc49880, 0xe0338e, 0xf01898, 0x881908, - 0x5c0947, 0x14205e, 0x08f69c, 0x641cae, 0x003de8, 0x48c796, 0xf4c248, - 0xf47190, 0x2802d8, 0x24181d, 0x641cb0, 0x54b802, 0xd4909c, 0xe4e0a6, - 0x00e091, 0x503cea, 0xb4f1da, 0x80b03d, 0xe49adc, 0xace4b5, 0xf06d78, - 0xd0d2b0, 0x448f17, 0x1cddea, 0x78886d, 0x20ee28, 0xb4f61c, 0x08f4ab, - 0x8c8590, 0x6c96cf, 0x508f4c, 0xd463c6, 0xdc44b6, 0x1007b6, 0x342d0d, - 0x54bd79, 0x30074d, 0x947be7, 0x5092b9, 0xdc74a8, 0x88d50c, 0xc4ae12, - 0x989e63, 0x886b6e, 0xd4dccd, 0x484baa, 0xd4ae05, 0xdca904, 0x6cab31, - 0x4c74bf, 0x5082d5, 0xf0ee10, 0xb81daa, 0x5caf06, 0x64b0a6, 0x7c04d0, - 0x84fcac, 0xdc0c5c, 0x70700d, 0x186590, 0xf86214, 0x784f43, 0x404d7f, - 0x1c48ce, 0x6c5c14, 0xcc61e5, 0x609ac1, 0x20dbab, 0x748d08, 0x9c8ba0, - 0xcc088d, 0x38a4ed, 0x0007ab, 0xe8e5d6, 0xc87e75, 0x00265f, 0x00233a, - 0x382dd1, 0x10ddb1, 0x00166c, 0x001599, 0x0012fb, 0xd0667b, 0x1c66aa, - 0x1489fd, 0xbc851f, 0x002491, 0x3c8bfe, 0xd4e8b2, 0xe8039a, 0x30cda7, - 0x2c4401, 0xb8d9ce, 0x002339, 0x5001bb, 0x001247, 0x0015b9, 0xb85e7b, - 0x1c77f6, 0x742344, 0xc8ba94, 0x843838, 0x54880e, 0xf025b7, 0xe492fb, - 0x6cb7f4, 0x181eb0, 0x5c3c27, 0xbc72b1, 0x78f7be, 0x684898, 0x3423ba, - 0x400e85, 0x88797e, 0xd013fd, 0x888322, 0xe89309, 0x6cd032, 0x3cbdd8, - 0x78c3e9, 0x64899a, 0xf8a9d0, 0xccfa00, 0xa816b2, 0x64bc0c, 0x74a722, - 0xf01c13, 0x344df7, 0x583f54, 0xc01ada, 0x8c1abf, 0x30cbf8, 0xa0cbfd, - 0xe45d75, 0x408805, 0x98398e, 0xd0fccc, 0xc09727, 0x84a134, 0x0c5101, - 0x2cf0a2, 0x68fb7e, 0x6c2483, 0x5cca1a, 0xac0d1b, 0x0034da, 0x202d07, - 0xb44bd2, 0xdc415f, 0x102ab3, 0xe8b2ac, 0xe49a79, 0xf45c89, 0x20768f, - 0x300d43, 0x607edd, 0x08373d, 0xc488e5, 0x1077b1, 0x64b853, 0x389496, - 0x5056bf, 0x90f1aa, 0x74458a, 0xfcc734, 0x8425db, 0xb0ec71, 0xe458b8, - 0x088c2c, 0xa49a58, 0x08ee8b, 0x68ed43, 0x70aab2, 0x000f86, 0xf8e079, - 0xccc3ea, 0x40786a, 0x38cada, 0x34ab37, 0x18af61, 0x5cf938, 0x002376, - 0x38e7d8, 0x188796, 0xb4cef6, 0xcc4463, 0x6c72e7, 0x741bb2, 0xf8a45f, - 0x640980, 0x185936, 0x60fec5, 0xe425e7, 0x7c6d62, 0x40d32d, 0xc42c03, - 0x9027e4, 0xbc926b, 0x101c0c, 0x080007, 0x0016cb, 0x0017f2, 0x001f5b, - 0x002436, 0x00254b, 0x6c3e6d, 0xbc6778, 0x20c9d0, 0x68967b, 0x581faa, - 0x88c663, 0xa46706, 0x8c5877, 0xa8fad8, 0x008865, 0xbc3baf, 0x3ce072, - 0x84fcfe, 0xe48b7f, 0xd8d1cb, 0xb817c2, 0x7c11be, 0x98d6bb, 0x283737, - 0x50ead6, 0x189efc, 0x804971, 0x7cf05f, 0x109add, 0x848e0c, 0x3c15c2, - 0x6c709f, 0x6476ba, 0x34e2fd, 0x04489a, 0x80e650, 0x90fd61, 0x2cf0ee, - 0x5c97f3, 0x8c2937, 0xaccf5c, 0x80006e, 0xa4c361, 0xb09fba, 0x0c4de9, - 0xe0f5c6, 0xa0edcd, 0x087045, 0xa88808, 0xc0f2fb, 0x24e314, 0xf0f61c, - 0x38484c, 0x38c986, 0xd03311, 0xd4f46f, 0x48437c, 0x34a395, 0x787e61, - 0x60f81d, 0x5cf5da, 0x18ee69, 0x649abe, 0xac293a, 0x9cfc01, 0x9c35eb, - 0xf099bf, 0x94e96a, 0x507a55, 0x5882a8, 0x24da9b, 0xe83a12, 0x80656d, - 0x38d40b, 0x209bcd, 0xfcf136, 0x18895b, 0x7cf90e, 0x50f0d3, 0x78bdbc, - 0x84119e, 0xe4907e, 0x149a10, 0x485073, 0x244b03, 0x74e28c, 0x244b81, - 0x60af6d, 0xb85a73, 0x981dfa, 0x102f6b, 0xc44202, 0x103047, 0xf884f2, - 0x5c2e59, 0xe0cbee, 0x002248, 0x8031f0, 0xe0d083, 0xd80b9a, 0x5c666c, - 0xc4e1a1, 0x1855e3, 0xe450eb, 0x886440, 0x6070c0, 0xf0c371, 0x987a14, - 0xc83ddc, 0x845733, 0x1819d6, 0xbc98df, 0x70dda8, 0x4c6f9c, 0x4883b4, - 0x0017fa, 0x941625, 0x34a8eb, 0xa483e7, 0xf4afe7, 0xac88fd, 0x20326c, - 0x6489f1, 0x2034fb, 0xa89ced, 0xe458e7, 0xdc080f, 0xf8e94e, 0xec2ce2, - 0x40bc60, 0xe83617, 0x9c648b, 0x344262, 0x14d00d, 0x703a51, 0x04e598, - 0x6cc7ec, 0xc0bdc8, 0x647bce, 0xa887b3, 0x6c006b, 0x24fce5, 0x9caa1b, - 0x70fd46, 0x8c83e1, 0x889f6f, 0x482ca0, 0x0ccb85, 0x08d46a, 0x68e7c2, - 0x58b10f, 0x645aed, 0xc0b658, 0x48a91c, 0x50bc96, 0xfc2a9c, 0xa056f3, - 0x549963, 0x90dd5d, 0xc819f7, 0x3880df, 0x3cdcbc, 0x804e70, 0xd4e6b7, - 0x04d13a, 0xc48466, 0x347c25, 0xcc2db7, 0x0c9838, 0xd41a3f, 0x5c865c, - 0x04b167, 0xec8350, 0x2c5491, 0xe42b34, 0x3c2ef9, 0xa04ea7, 0xf0989d, - 0xd80831, 0x10f1f2, 0x982d68, 0xb019c6, 0x3866f0, 0x703eac, 0x7c2edd, - 0x3cf7a4, 0x986f60, 0xc49ded, 0x9810e8, 0xc0d012, 0xbca920, 0x48a195, - 0xf80377, 0x8058f8, 0xdca4ca, 0x8c8fe9, 0x5cba37, 0x2c200b, 0x8866a5, - 0xacc1ee, 0x805a04, 0xbc8385, 0x2c598a, 0xe47dbd, 0x001cb3, 0x24920e, - 0xfc4203, 0xa01081, 0xf07960, 0xa0d795, 0xbc4760, 0x04180f, 0x2013e0, - 0x002566, 0xc09f05, 0x606bbd, 0x00214c, 0x0018af, 0x001ee1, 0x00166b, - 0x0000f0, 0x8cc8cd, 0xa8f274, 0xd487d8, 0x184617, 0x380a94, 0xd0dfc7, - 0xd0c1b1, 0x8018a7, 0xf47b5e, 0x70f927, 0xc45006, 0x88329b, 0x1449e0, - 0xd02544, 0xbc4486, 0x20d390, 0x9401c2, 0x50fc9f, 0x380b40, 0xb8ff61, - 0xf0728c, 0x34aa8b, 0x24dbed, 0x68c44d, 0x440444, 0x84b541, 0x006f64, - 0xdc6672, 0xf8e61a, 0xcc2d8c, 0x98d6f7, 0x700514, 0xe892a4, 0x10683f, - 0x40b0fa, 0x0025e5, 0x0021fb, 0x34fcef, 0xbcf5ac, 0x0c4885, 0x0022a9, - 0x9c2a83, 0xa039f7, 0xb0e235, 0x14c913, 0xe09861, 0xa06090, 0xbc765e, - 0xd85b2a, 0x90c1c6, 0x70a2b3, 0xf40f24, 0x4c57ca, 0xc46ab7, 0x48e9f1, - 0xc83f26, 0x40163b, 0xc83870, 0x6c8fb5, 0x1c9e46, 0xc0ccf8, 0x9c4fda, - 0x8489ad, 0x647791, 0x9ce6e7, 0x9c0298, 0x28987b, 0x54fa3e, 0x0c8910, - 0x78abbb, 0xd8c4e9, 0xbcd11f, 0xf4428f, 0x446d6c, 0x00f46f, 0x0c715d, - 0x34bb1f, 0x406f2a, 0x985fd3, 0x60beb5, 0xf8f1b6, 0xf4f1e1, 0x9cd917, - 0x9068c3, 0x68dbca, 0x086698, 0xbc5436, 0x044bed, 0x6c8dc1, 0x0cd746, - 0x60a37d, 0xd40b1a, 0xfc64ba, 0x9060f1, 0xa4516f, 0x68dfdd, 0x98fae3, - 0xf0b429, 0xb8782e, 0x000502, 0x000a95, 0x00264a, 0x041e64, 0x001124, - 0x002241, 0x7cc537, 0x78ca39, 0x18e7f4, 0x70cd60, 0x8c7b9d, 0xd89e3f, - 0xb8c75d, 0x0c74c2, 0x403004, 0x842999, 0x74e2f5, 0xe0c97a, 0x68a86d, - 0x7cc3a1, 0x7073cb, 0x90840d, 0xe80688, 0xec852f, 0x00f4b9, 0x5c95ae, - 0x9803d8, 0x60c547, 0x685b35, 0x2cb43a, 0x689c70, 0x380f4a, 0x3010e4, - 0xa886dd, 0x444c0c, 0xb4f0ab, 0x80929f, 0x9c04eb, 0x5c969d, 0x609217, - 0x84b153, 0xe06678, 0x48d705, 0x041552, 0xcc785f, 0x88cb87, 0xf0c1f1, - 0x843835, 0x8c006d, 0xa8968a, 0xf41ba1, 0x60d9c7, 0x3cab8e, 0xf82793, - 0x907240, 0x908d6c, 0xb8098a, 0x4c7c5f, 0x68644b, 0xc81ee7, 0xa43135, - 0x68d93c, 0x00f76f, 0xc88550, 0x7014a6, 0x985aeb, 0x78d75f, 0xe0b52d, - 0x6c94f8, 0xc0cecd, 0x2cae2b, 0xc869cd, 0xa4b805, 0x5cadcf, 0xbc6c21, - 0xf40e22, 0x544e90, 0xc01173, 0xbce63f, 0x7c9122, 0xacbc32, 0x2827bf, - 0x800184, 0x8cbfa6, 0xc8a823, 0xb0c559, 0xbc1485, 0x9c6c15, 0xc0335e, - 0xd0929e, 0x083d88, 0x608f5c, 0x109266, 0x281878, 0x847a88, 0xa0f450, - 0xa826d9, 0x00155d, 0x00125a, 0x000d3a, 0x0003ff, 0x18d0c5, 0x1cccd6, - 0x582059, 0x74e1b6, 0xf40e01, 0x1495ce, 0x50de06, 0xcc660a, 0xfc1d43, - 0x0024e9, 0xb4c4fc, 0x7cd661, 0x98b8ba, 0x103025, 0xb8b2f8, 0x98460a, - 0xb85d0a, 0x7c9a1d, 0x5462e2, 0x149d99, 0x489507, 0xd85575, 0xd411a3, - 0x04ba8d, 0xe446da, 0x58d9c3, 0x3c8375, 0xb8bc5b, 0xd8ce3a, 0xb8c74a, - 0x94f6d6, 0xf82d7c, 0xc09ad0, 0x94b01f, 0x90633b, 0xfcaab6, 0x782327, - 0xdcf756, 0x74b587, 0xfcb6d8, 0x18810e, 0x608c4a, 0x241b7a, 0x8cfe57, - 0xc0a600, 0xa823fe, 0x1c427d, 0xcc2119, 0x304b07, 0x000393, 0x702ad5, - 0x74eb80, 0x0ce0dc, 0xd868c3, 0xc493d9, 0xa82bb9, 0x1c1ac0, 0x88ae07, - 0x40831d, 0xdcd3a2, 0x5c1dd9, 0x68fef7, 0xd07714, 0x587a6a, 0x705aac, - 0x685acf, 0x0ca8a7, 0x2c61f6, 0xd4a33d, 0xf0766f, 0x4098ad, 0x6c4d73, - 0x68ef43, 0xd02b20, 0xd86375, 0x30b4b8, 0x9c8c6e, 0x18f0e4, 0x00bf61, - 0xd00401, 0xdc5583, 0xb8c111, 0x9ce33f, 0x7867d7, 0x087808, 0x887598, - 0xc0174d, 0xa407b6, 0x04d6aa, 0x08152f, 0xf4f5db, 0xc0a53e, 0xecd09f, - 0xa8be27, 0x94d029, 0x308454, 0x5c5181, 0x608e08, 0x4c189a, 0x58c5cb, - 0xb4bff6, 0x74f61c, 0x84c0ef, 0xa8b86e, 0xc40bcb, 0xb83765, 0x14bd61, - 0xc0d3c0, 0x948bc1, 0x14568e, 0xd4619d, 0x7c5049, 0x64b473, 0x7451ba, - 0x7802f8, 0xec01ee, 0x682737, 0x58404e, 0xd0c5f3, 0xbc9fef, 0x20ab37, - 0x60f445, 0x2c0e3d, 0x88e87f, 0x9cf48e, 0x5cf7e6, 0xb853ac, 0x203cae, - 0x2cbaba, 0x40d3ae, 0xa03be3, 0x4c3275, 0x3816d1, 0xd0176a, 0xd48890, - 0x5492be, 0x949aa9, 0x001c43, 0x002399, 0x0017c9, 0x7cf854, 0xc4731e, - 0xec1f72, 0xe4121d, 0xe8508b, 0xf8042e, 0x0023d6, 0x001b98, 0x001a8a, - 0x3c5a37, 0xf49f54, 0x34c3ac, 0x44f459, 0x00265d, 0x002567, 0xbcb1f3, - 0xc462ea, 0x182666, 0x30d6c9, 0xcc07ab, 0x1c62b8, 0xccf9e8, 0xd857ef, - 0x18e2c2, 0x28bab5, 0xe440e2, 0x103b59, 0xd890e8, 0x9852b1, 0x14f42a, - 0x0808c2, 0xccfe3c, 0xb43a28, 0x78a873, 0xd83062, 0xc041f6, 0x001e75, - 0x001c62, 0xf895c7, 0x34145f, 0x0821ef, 0x505527, 0x88c9d0, 0x8c3ae3, - 0x00aa70, 0x60e3ac, 0x4c0bbe, 0xb88d12, 0x845181, 0x348a7b, 0x78009e, - 0xacc33a, 0x54f201, 0x70288b, 0x8c8ef2, 0x90b0ed, 0x04d3cf, 0x1c3ade, - 0x509ea7, 0xa88195, 0x88add2, 0xac5f3e, 0xb48b19, 0xbcec5d, 0x28a02b, - 0xb8c68e, 0x04fe31, 0x4cbca5, 0xd831cf, 0x188331, 0x9c65b0, 0x8455a5, - 0xa87c01, 0x50f520, 0x64b310, 0xa4ebd3, 0xb0d09c, 0x50c8e5, 0x5cf6dc, - 0x0026ff, 0xa4e4b8, 0xf40b93, 0x1c69a5, 0x94ebcd, 0x141aa3, 0x1430c6, - 0x88074b, 0x3871de, 0x7081eb, 0x78f882, 0x606944, 0x807abf, 0x00092d, - 0x7c6193, 0x0452f3, 0x90e7c4, 0xa81b5a, 0x2c5bb8, 0xacf7f3, 0xd4970b, - 0x8cbebe, 0x14f65a, 0x009ec8, 0x0c1daf, 0x3480b3, 0xf48b32, 0x60fb42, - 0x64b9e8, 0xd8a25e, 0x000a27, 0x001d4f, 0x002312, 0x60facd, 0x003ee1, - 0xfc253f, 0x183451, 0x0c771a, 0x286aba, 0x3451c9, 0x406c8f, 0xd023db, - 0x70dee2, 0x5855ca, 0xdc2b61, 0x40a6d9, 0x4cb199, 0xc09f42, 0x705681, - 0x040cce, 0x403cfc, 0x4860bc, 0xf0cba1, 0x182032, 0x34c059, 0xf0d1a9, - 0x14109f, 0x04f7e4, 0xdc9b9c, 0x54724f, 0x8c7c92, 0xb03495, 0x042665, - 0xec3586, 0x54eaa8, 0x28e14c, 0xe4c63d, 0x54e43a, 0x04db56, 0x04e536, - 0xe8802e, 0x006171, 0x6c4008, 0x7c6df8, 0x78fd94, 0x2cbe08, 0xac3c0b, - 0x701124, 0xf437b7, 0xd8cf9c, 0xa8bbcf, 0xac7f3e, 0x280b5c, 0xacfdec, - 0x28f076, 0x20a2e4, 0xbc4cc4, 0xdc3714, 0x40331a, 0xccc760, 0x7c0191, - 0x84100d, 0x80d605, 0xdc2b2a, 0x48137e, 0x382de8, 0xc08997, 0x04c23e, - 0x380195, 0xb4ae2b, 0x5c5188, 0x0ce725, 0xe0db10, 0x1432d1, 0x1816c9, - 0x842e27, 0x0c413e, 0x9000db, 0xb4ef39, 0xc808e9, 0x183a2d, 0x8463d6, - 0xb84fd5, 0xc0eefb, 0x206e9c, 0x6c2f2c, 0x18227e, 0x30c7ae, 0x501ac5, - 0x6045bd, 0x1cb094, 0xe85b5b, 0x0025ae, 0x001dd8, 0x000a75, 0xa4c939, - 0xc0dcda, 0x04b429, 0x48794d, -}; \ No newline at end of file diff --git a/src/blecsan.cpp b/src/blecsan.cpp index f39a2e58..a6a0d999 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -151,7 +151,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, ESP_LOGV(TAG, "RSSI : %d", p->scan_rst.rssi); #endif -#if (VENDORFILTER) +#if (MACFILTER) if ((p->scan_rst.ble_addr_type == BLE_ADDR_TYPE_RANDOM) || (p->scan_rst.ble_addr_type == BLE_ADDR_TYPE_RPA_RANDOM)) { #ifdef VERBOSE @@ -176,7 +176,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, mac_add((uint8_t *)p->scan_rst.bda, p->scan_rst.rssi, MAC_SNIFF_BLE); #endif - /* to be improved in vendorfilter if: + /* to be improved in macfilter: // you can search for elements in the payload using the // function esp_ble_resolve_adv_data() // @@ -187,7 +187,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, ESP_BLE_AD_TYPE_NAME_CMPL, &len); filter BLE devices using their advertisements to get filter alternative - to vendor OUI if vendorfiltering is on, we ... + to vendor OUI if macfiltering is on, we ... - want to count: mobile phones and tablets - don't want to count: beacons, peripherals (earphones, headsets, printers), cars and machines see @@ -225,11 +225,11 @@ esp_err_t register_ble_callback(void) { .scan_type = BLE_SCAN_TYPE_PASSIVE, .own_addr_type = BLE_ADDR_TYPE_RANDOM, -#if (VENDORFILTER) +#if (MACFILTER) .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for broadcasting // data in broadcast applications (e.g., Beacons), so we don't want them in - // vendorfilter mode + // macfilter mode #else .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, #endif diff --git a/src/configmanager.cpp b/src/configmanager.cpp index a489801e..8a9da4ce 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -48,14 +48,14 @@ static void defaultConfig(configData_t *myconfig) { myconfig->blescantime = BLESCANINTERVAL / 10; // BT channel scan cycle [seconds/100], default 1 (= 10ms) - myconfig->blescan = 1; // 0=disabled, 1=enabled - myconfig->wifiscan = 1; // 0=disabled, 1=enabled - myconfig->wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4) - myconfig->vendorfilter = VENDORFILTER; // 0=disabled, 1=enabled - myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) - myconfig->monitormode = 0; // 0=disabled, 1=enabled - myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default - myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled + myconfig->blescan = 1; // 0=disabled, 1=enabled + myconfig->wifiscan = 1; // 0=disabled, 1=enabled + myconfig->wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4) + myconfig->macfilter = MACFILTER; // 0=disabled, 1=enabled + myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) + myconfig->monitormode = 0; // 0=disabled, 1=enabled + myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default + myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled #ifdef HAS_BME680 // initial BSEC state for BME680 sensor diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 7d0e4bea..2f5446f2 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -3,10 +3,6 @@ #include "globals.h" #include "macsniff.h" -#if (VENDORFILTER) -#include "vendor_array.h" -#endif - // Local logging tag static const char TAG[] = __FILE__; @@ -130,15 +126,6 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { } }; -#if (VENDORFILTER) - uint32_t *oui; // temporary buffer for vendor OUI - oui = (uint32_t *)MacBuffer.mac; - // if we find OUI on vendor filter list we don't analyze and return early - if (std::find(vendors.begin(), vendors.end(), __builtin_bswap32(*oui) >> 8) == - vendors.end()) - return 0; -#endif - char buff[10]; // temporary buffer for printf uint32_t *mac; // temporary buffer for shortened MAC diff --git a/src/main.cpp b/src/main.cpp index 6bba631a..b567454a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -374,7 +374,7 @@ void setup() { strcat_P(features, " SDS"); #endif -#if (VENDORFILTER) +#if (MACFILTER) strcat_P(features, " FILTER"); #endif diff --git a/src/paxcounter_orig.conf b/src/paxcounter_orig.conf index c0dc5816..4c1a7225 100644 --- a/src/paxcounter_orig.conf +++ b/src/paxcounter_orig.conf @@ -16,7 +16,7 @@ #define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed // MAC sniffing parameters -#define VENDORFILTER 0 // set to 0 if you want to scan all devices, not filtering smartphone OUIs +#define MACFILTER 1 // set to 0 if you want to scan all devices, 1 to scan only devices with random MACs (aka smartphones) [default = 1] #define BLECOUNTER 1 // set to 0 if you do not want to install the BLE sniffer #define WIFICOUNTER 1 // set to 0 if you do not want to install the WIFI sniffer #define MAC_QUEUE_SIZE 50 // size of MAC processing buffer (number of MACs) [default = 50] diff --git a/src/payload.cpp b/src/payload.cpp index 112a544d..904e454c 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -49,7 +49,7 @@ void PayloadConvert::addConfig(configData_t value) { buffer[cursor++] = value.blescantime; buffer[cursor++] = value.blescan; buffer[cursor++] = value.wifiant; - buffer[cursor++] = value.vendorfilter; + buffer[cursor++] = value.macfilter; buffer[cursor++] = value.rgblum; buffer[cursor++] = value.payloadmask; buffer[cursor++] = value.monitormode; @@ -179,8 +179,7 @@ void PayloadConvert::addConfig(configData_t value) { writeBitmap(value.adrmode ? true : false, value.screensaver ? true : false, value.screenon ? true : false, value.countermode ? true : false, value.blescan ? true : false, value.wifiant ? true : false, - value.vendorfilter ? true : false, - value.monitormode ? true : false); + value.macfilter ? true : false, value.monitormode ? true : false); writeUint8(value.payloadmask); writeVersion(value.version); } diff --git a/src/rcommand.cpp b/src/rcommand.cpp index c18db679..0759dc79 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -234,18 +234,18 @@ void set_loraadr(uint8_t val[]) { void set_blescan(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: set BLE scanner to %s", val[0] ? "on" : "off"); + macs_ble = 0; // clear BLE counter cfg.blescan = val[0] ? 1 : 0; if (cfg.blescan) start_BLEscan(); - else { - macs_ble = 0; // clear BLE counter + else stop_BLEscan(); - } } void set_wifiscan(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: set WIFI scanner to %s", val[0] ? "on" : "off"); + macs_wifi = 0; // clear WIFI counter cfg.wifiscan = val[0] ? 1 : 0; switch_wifi_sniffer(cfg.wifiscan); } @@ -259,10 +259,10 @@ void set_wifiant(uint8_t val[]) { #endif } -void set_vendorfilter(uint8_t val[]) { - ESP_LOGI(TAG, "Remote command: set vendorfilter mode to %s", +void set_macfilter(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: set macfilter mode to %s", val[0] ? "on" : "off"); - cfg.vendorfilter = val[0] ? 1 : 0; + cfg.macfilter = val[0] ? 1 : 0; } void set_rgblum(uint8_t val[]) { @@ -378,7 +378,7 @@ static const cmd_t table[] = { {0x07, set_loraadr, 1, true}, {0x08, set_screensaver, 1, true}, {0x09, set_reset, 1, false}, {0x0a, set_sendcycle, 1, true}, {0x0b, set_wifichancycle, 1, true}, {0x0c, set_blescantime, 1, true}, - {0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true}, + {0x0d, set_macfilter, 1, false}, {0x0e, set_blescan, 1, true}, {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, {0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true}, diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index ccb6a87e..4b85f1ec 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -35,8 +35,14 @@ IRAM_ATTR void wifi_sniffer_packet_handler(void *buff, (wifi_ieee80211_packet_t *)ppkt->payload; const wifi_ieee80211_mac_hdr_t *hdr = &ipkt->hdr; - // process seen MAC - mac_add((uint8_t *)hdr->addr2, ppkt->rx_ctrl.rssi, MAC_SNIFF_WIFI); +// process seen MAC +#if MACFILTER + // we guess it's a smartphone, if randomization bit of MAC is set + if (!(hdr->addr2[0] & 0b10)) + return; + else +#endif + mac_add((uint8_t *)hdr->addr2, ppkt->rx_ctrl.rssi, MAC_SNIFF_WIFI); } // Software-timer driven Wifi channel rotation callback function From 19216faa884751e86e7eb420a5b0ae0df89d35ed Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 13 Dec 2020 19:30:16 +0100 Subject: [PATCH 025/104] rework wifi init to remove event_handler error --- src/wifiscan.cpp | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index 4b85f1ec..bc69699b 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -7,10 +7,6 @@ static const char TAG[] = "wifi"; TimerHandle_t WifiChanTimer; -static wifi_country_t wifi_country = {WIFI_MY_COUNTRY, WIFI_CHANNEL_MIN, - WIFI_CHANNEL_MAX, 100, - WIFI_COUNTRY_POLICY_MANUAL}; - typedef struct { unsigned frame_ctrl : 16; unsigned duration_id : 16; @@ -53,26 +49,30 @@ void switchWifiChannel(TimerHandle_t xTimer) { } void wifi_sniffer_init(void) { - wifi_init_config_t wificfg = WIFI_INIT_CONFIG_DEFAULT(); - wificfg.nvs_enable = 0; // we don't need any wifi settings from NVRAM - wificfg.wifi_task_core_id = 0; // we want wifi task running on core 0 - // wifi_promiscuous_filter_t filter = { - // .filter_mask = WIFI_PROMIS_FILTER_MASK_MGMT}; // only MGMT frames - // .filter_mask = WIFI_PROMIS_FILTER_MASK_ALL}; // we use all frames + wifi_country_t wifi_country = {WIFI_MY_COUNTRY, WIFI_CHANNEL_MIN, + WIFI_CHANNEL_MAX, 100, + WIFI_COUNTRY_POLICY_MANUAL}; - wifi_promiscuous_filter_t filter = {.filter_mask = - WIFI_PROMIS_FILTER_MASK_MGMT | - WIFI_PROMIS_FILTER_MASK_DATA}; + wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); + wifi_cfg.event_handler = NULL; // we don't need a wifi event handler + wifi_cfg.nvs_enable = 0; // we don't need any wifi settings from NVRAM + wifi_cfg.wifi_task_core_id = 0; // we want wifi task running on core 0 - ESP_ERROR_CHECK(esp_wifi_init(&wificfg)); // configure Wifi with cfg + wifi_promiscuous_filter_t wifi_filter = {.filter_mask = + WIFI_PROMIS_FILTER_MASK_MGMT | + WIFI_PROMIS_FILTER_MASK_DATA}; + + ESP_ERROR_CHECK(esp_event_loop_init(NULL, NULL)); + ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg)); // start Wifi task ESP_ERROR_CHECK( esp_wifi_set_country(&wifi_country)); // set locales for RF and channels - ESP_ERROR_CHECK( - esp_wifi_set_storage(WIFI_STORAGE_RAM)); // we don't need NVRAM + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // no modem power saving - ESP_ERROR_CHECK(esp_wifi_set_promiscuous_filter(&filter)); // set frame filter + + ESP_ERROR_CHECK( + esp_wifi_set_promiscuous_filter(&wifi_filter)); // set frame filter ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler)); ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); // now switch on monitor mode @@ -97,7 +97,6 @@ void switch_wifi_sniffer(uint8_t state) { xTimerStart(WifiChanTimer, (TickType_t)0); } else { // switch wifi sniffer off - macs_wifi = 0; // clear WIFI counter if (xTimerIsTimerActive(WifiChanTimer) != pdFALSE) xTimerStop(WifiChanTimer, (TickType_t)0); esp_wifi_set_promiscuous(false); From 570a3ea486777e9be9e0eaac790340bf7b3d1911 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 15 Dec 2020 22:36:52 +0100 Subject: [PATCH 026/104] wifiscan.cpp: remove legay event loop handler --- src/wifiscan.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index bc69699b..9e9c480e 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -63,7 +63,6 @@ void wifi_sniffer_init(void) { WIFI_PROMIS_FILTER_MASK_MGMT | WIFI_PROMIS_FILTER_MASK_DATA}; - ESP_ERROR_CHECK(esp_event_loop_init(NULL, NULL)); ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg)); // start Wifi task ESP_ERROR_CHECK( esp_wifi_set_country(&wifi_country)); // set locales for RF and channels From 973635e0e5f5d607196520f9f986442c429cfe0d Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 16 Dec 2020 09:36:16 +0100 Subject: [PATCH 027/104] reset.cpp: no sleep while joining --- src/reset.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/reset.cpp b/src/reset.cpp index c3983459..401a5911 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -70,6 +70,12 @@ void do_after_reset(void) { void enter_deepsleep(const uint64_t wakeup_sec = 60, gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { +#if (HAS_LORA) + if (!LMIC.devaddr) + ESP_LOGI(TAG, "Can't go to sleep while joining"); + return; +#endif + int i; // validate wake up pin, if we have From 808e5e51923693913ba0a49f3744cf6cba1f1cef Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 16 Dec 2020 11:57:40 +0100 Subject: [PATCH 028/104] bugfix no sleep while joining --- src/reset.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index 401a5911..f740d002 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -71,9 +71,10 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { #if (HAS_LORA) - if (!LMIC.devaddr) + if (!LMIC.devaddr) { ESP_LOGI(TAG, "Can't go to sleep while joining"); - return; + return; + } #endif int i; From 3fb5d258b74c145e48bd00c2a546d5e62ecacaf8 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 16 Dec 2020 18:32:59 +0100 Subject: [PATCH 029/104] PR #683 --- src/power.cpp | 12 ++++++++++-- src/reset.cpp | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index 9b16995e..a813eb60 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -55,7 +55,6 @@ void AXP192_powerevent_IRQ(void) { // long press -> shutdown power, can be exited by another longpress if (pmu.isPEKLongtPressIRQ()) { AXP192_power(pmu_power_off); // switch off Lora, GPS, display - pmu.shutdown(); // switch off device } pmu.clearIRQ(); @@ -73,23 +72,32 @@ void AXP192_power(pmu_power_t powerlevel) { pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); - // pmu.setPowerOutPut(AXP192_DCDC3, AXP202_OFF); + pmu.shutdown(); break; case pmu_power_sleep: +#ifdef PMU_LED_SLEEP_MODE + pmu.setChgLEDMode(PMU_LED_SLEEP_MODE); +#else pmu.setChgLEDMode(AXP20X_LED_OFF); +#endif // we don't cut off DCDC1, because then display blocks i2c bus pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // gps off pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // lora off break; + case pmu_power_on: default: // all rails power on pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON); // Lora on T-Beam V1.0 pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON); // Gps on T-Beam V1.0 pmu.setPowerOutPut(AXP192_DCDC1, AXP202_ON); // OLED on T-Beam v1.0 pmu.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // unused on T-Beam v1.0 pmu.setPowerOutPut(AXP192_EXTEN, AXP202_OFF); // unused on T-Beam v1.0 +#ifdef PMU_LED_RUN_MODE + pmu.setChgLEDMode(PMU_LED_RUN_MODE); +#else pmu.setChgLEDMode(AXP20X_LED_LOW_LEVEL); +#endif break; } } diff --git a/src/reset.cpp b/src/reset.cpp index f740d002..00668e0b 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -51,6 +51,10 @@ void do_after_reset(void) { ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); RTC_runmode = RUNMODE_WAKEUP; +// power on all rails if has PMU +#ifdef HAS_PMU + AXP192_power(pmu_power_on); +#endif break; case ESP_SLEEP_WAKEUP_ALL: From 93e194506f94acbda6f35e1a8f9dcd9d00ca58a4 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 16 Dec 2020 18:54:23 +0100 Subject: [PATCH 030/104] PR #683 /2 --- src/power.cpp | 6 +++--- src/reset.cpp | 4 ---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index a813eb60..3dee573c 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -140,9 +140,6 @@ void AXP192_init(void) { pmu.adc1Enable(AXP202_VBUS_VOL_ADC1, true); pmu.adc1Enable(AXP202_VBUS_CUR_ADC1, true); - // switch power rails on - AXP192_power(pmu_power_on); - #ifdef PMU_INT pinMode(PMU_INT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(PMU_INT), PMUIRQ, FALLING); @@ -161,6 +158,9 @@ void AXP192_init(void) { pmu.enableChargeing(true); #endif + // switch power rails on + AXP192_power(pmu_power_on); + ESP_LOGI(TAG, "AXP192 PMU initialized"); } } diff --git a/src/reset.cpp b/src/reset.cpp index 00668e0b..f740d002 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -51,10 +51,6 @@ void do_after_reset(void) { ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); RTC_runmode = RUNMODE_WAKEUP; -// power on all rails if has PMU -#ifdef HAS_PMU - AXP192_power(pmu_power_on); -#endif break; case ESP_SLEEP_WAKEUP_ALL: From 590cc9dd5ccb179d827517ac21dc27f636e4ec08 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 16 Dec 2020 18:54:38 +0100 Subject: [PATCH 031/104] skip i2cscan after wakeup from sleep --- src/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index b567454a..33a7a650 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -208,7 +208,8 @@ void setup() { _ASSERT(loadConfig()); // includes initialize if necessary // now that we are powered, we scan i2c bus for devices - i2c_scan(); + if (RTC_runmode == RUNMODE_POWERCYCLE) + i2c_scan(); // initialize display #ifdef HAS_DISPLAY From 49a0c703c104deb4a85308b8482a613d32a4a05b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 18 Dec 2020 18:38:44 +0100 Subject: [PATCH 032/104] wifiscan.cpp: sanitized mac filter condition --- src/wifiscan.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index 9e9c480e..edcafe13 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -33,8 +33,9 @@ IRAM_ATTR void wifi_sniffer_packet_handler(void *buff, // process seen MAC #if MACFILTER - // we guess it's a smartphone, if randomization bit of MAC is set - if (!(hdr->addr2[0] & 0b10)) + // we guess it's a smartphone, if U/L bit #2 of MAC is set + // bit #2 = 1 -> local mac (randomized) / bit #2 = 0 -> universal mac + if ((hdr->addr2[0] & 0b10) == 0) return; else #endif From 284d02cb877b210cfa068aa70b7d98ccca6b372b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 18 Dec 2020 18:39:28 +0100 Subject: [PATCH 033/104] blescan.cpp: avoid ble stack crash after deinit --- src/blecsan.cpp | 89 ++++++++++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 38 deletions(-) diff --git a/src/blecsan.cpp b/src/blecsan.cpp index a6a0d999..6fa9ba9c 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -214,35 +214,53 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, } // switch } // gap_callback_handler -esp_err_t register_ble_callback(void) { - ESP_LOGI(TAG, "Register GAP callback"); +esp_err_t register_ble_callback(bool unregister = false) { - // This function is called when gap event occurs, such as scan result. - // register the scan callback function to the gap module - ESP_ERROR_CHECK(esp_ble_gap_register_callback(&gap_callback_handler)); + if (unregister) { - static esp_ble_scan_params_t ble_scan_params = { - .scan_type = BLE_SCAN_TYPE_PASSIVE, - .own_addr_type = BLE_ADDR_TYPE_RANDOM, + ESP_LOGI(TAG, "Unregister GAP callback..."); + ESP_ERROR_CHECK(esp_ble_gap_stop_scanning()); + ESP_ERROR_CHECK(esp_ble_gap_register_callback(NULL)); -#if (MACFILTER) - .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, - // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for broadcasting - // data in broadcast applications (e.g., Beacons), so we don't want them in - // macfilter mode -#else - .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, -#endif + } - .scan_interval = - (uint16_t)(cfg.blescantime * 10 / 0.625), // Time = N * 0.625 msec - .scan_window = (uint16_t)(BLESCANWINDOW / 0.625) // Time = N * 0.625 msec - }; + else { - ESP_LOGI(TAG, "Set GAP scan parameters"); + ESP_LOGI(TAG, "Register GAP callback..."); - // This function is called to set scan parameters. - ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&ble_scan_params)); + // This function is called when gap event occurs, such as scan result. + // register the scan callback function to the gap module + ESP_ERROR_CHECK(esp_ble_gap_register_callback(&gap_callback_handler)); + + static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_PASSIVE, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + + /* + #if (MACFILTER) + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, + // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for + broadcasting + // data in broadcast applications (e.g., Beacons), so we don't want + them in + // macfilter mode + #else + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + #endif + */ + + .scan_interval = + (uint16_t)(cfg.blescantime * 10 / 0.625), // Time = N * 0.625 msec + .scan_window = + (uint16_t)(BLESCANWINDOW / 0.625) // Time = N * 0.625 msec + }; + + ESP_LOGI(TAG, "Set GAP scan parameters"); + + // This function is called to set scan parameters. + ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&ble_scan_params)); + } return ESP_OK; @@ -251,35 +269,30 @@ esp_err_t register_ble_callback(void) { void start_BLEscan(void) { #if (BLECOUNTER) ESP_LOGI(TAG, "Initializing bluetooth scanner ..."); - // Initialize BT controller to allocate task and other resource. - ESP_ERROR_CHECK(esp_coex_preference_set(ESP_COEX_PREFER_BT)); - if (!btStart()) { // enable bt_controller + if (btStart()) { // enable bt_controller + ESP_ERROR_CHECK(esp_coex_preference_set(ESP_COEX_PREFER_BT)); + ESP_ERROR_CHECK(esp_bluedroid_init()); + ESP_ERROR_CHECK(esp_bluedroid_enable()); + // Register callback function for capturing bluetooth packets + ESP_ERROR_CHECK(register_ble_callback(true)); + ESP_LOGI(TAG, "Bluetooth scanner started"); +#endif // BLECOUNTER + } else { ESP_LOGE(TAG, "Bluetooth controller start failed. Resetting device"); do_reset(true); } - ESP_ERROR_CHECK(esp_bluedroid_init()); - ESP_ERROR_CHECK(esp_bluedroid_enable()); - - // Register callback function for capturing bluetooth packets - ESP_ERROR_CHECK(register_ble_callback()); - - ESP_LOGI(TAG, "Bluetooth scanner started"); -#endif // BLECOUNTER } // start_BLEscan void stop_BLEscan(void) { #if (BLECOUNTER) ESP_LOGI(TAG, "Shutting down bluetooth scanner ..."); - - ESP_LOGD(TAG, "unregister GAP callback..."); - ESP_ERROR_CHECK(esp_ble_gap_register_callback(NULL)); + ESP_ERROR_CHECK(register_ble_callback(false)); // unregister capture function ESP_LOGD(TAG, "bluedroid disable..."); ESP_ERROR_CHECK(esp_bluedroid_disable()); ESP_LOGD(TAG, "bluedroid deinit..."); ESP_ERROR_CHECK(esp_bluedroid_deinit()); - if (!btStop()) { // disable bt_controller ESP_LOGE(TAG, "Bluetooth controller stop failed. Resetting device"); do_reset(true); From 4c9862e7d5055523da16299a12bf436940aeba39 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 18 Dec 2020 18:40:26 +0100 Subject: [PATCH 034/104] ttgobeam10.h: add blue LED control --- src/hal/ttgobeam10.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h index b2ff5b45..568fda1a 100644 --- a/src/hal/ttgobeam10.h +++ b/src/hal/ttgobeam10.h @@ -20,7 +20,7 @@ User, long press -> send LORA message Reset -> reset device */ -#define HAS_DISPLAY 1 +//#define HAS_DISPLAY 1 #define MY_DISPLAY_SDA SDA #define MY_DISPLAY_SCL SCL #define MY_DISPLAY_RST NOT_A_PIN @@ -42,6 +42,12 @@ Reset -> reset device // possible values (V): // 4_1/4_15/4_2/4_36 +// blue onboard led settings +// possible values: +// AXP20X_LED_OFF / AXP20X_LED_LOW_LEVEL (means LED ON) / AXP20X_LED_BLINK_1HZ / AXP20X_LED_BLINK_4HZ +#define PMU_LED_RUN_MODE AXP20X_LED_LOW_LEVEL +#define PMU_LED_SLEEP_MODE AXP20X_LED_BLINK_1HZ + // GPS settings #define HAS_GPS 1 // use on board GPS #define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_34, GPIO_NUM_12 // UBlox NEO 6M @@ -49,9 +55,9 @@ Reset -> reset device // enable only if device has these sensors, otherwise comment these lines // BME680 sensor on I2C bus -#define HAS_BME 1 // Enable BME sensors in general -#define HAS_BME680 SDA, SCL -#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! +//#define HAS_BME 1 // Enable BME sensors in general +//#define HAS_BME680 SDA, SCL +//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! //#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature From 015658962b3cdcb1f5abd907e1650eebdeb43a8d Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 18 Dec 2020 18:42:03 +0100 Subject: [PATCH 035/104] v2.0.6 --- platformio_orig.ini | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/platformio_orig.ini b/platformio_orig.ini index 883960b2..41f86fff 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -47,7 +47,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 2.0.51 +release_version = 2.0.6 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 @@ -60,7 +60,7 @@ monitor_speed = 115200 upload_speed = 115200 ; set by build.py and taken from hal file display_library = ; set by build.py and taken from hal file lib_deps_lora = - mcci-catena/MCCI LoRaWAN LMIC library @ ^3.2.0 + mcci-catena/MCCI LoRaWAN LMIC library @ ^3.3.0 lib_deps_display = bitbank2/OneBitDisplay @ 1.9.0 bitbank2/BitBang_I2C @ ^2.1.3 @@ -134,5 +134,4 @@ upload_protocol = esptool [env:dev] upload_protocol = esptool build_type = debug -platform = https://github.com/platformio/platform-espressif32.git#develop -platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git \ No newline at end of file +platform = https://github.com/platformio/platform-espressif32.git#develop \ No newline at end of file From 85f96455994ec8f41330a92127038dc940a256ab Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 18 Dec 2020 19:06:44 +0100 Subject: [PATCH 036/104] blescan.cpp bugfix unregister gap cb --- src/blecsan.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/blecsan.cpp b/src/blecsan.cpp index 6fa9ba9c..5deb6004 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -237,18 +237,16 @@ esp_err_t register_ble_callback(bool unregister = false) { .own_addr_type = BLE_ADDR_TYPE_RANDOM, .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, - /* +/* #if (MACFILTER) .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, - // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for - broadcasting - // data in broadcast applications (e.g., Beacons), so we don't want - them in + // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for broadcasting + // data in broadcast applications (e.g., Beacons), so we don't want them in // macfilter mode #else .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, #endif - */ +*/ .scan_interval = (uint16_t)(cfg.blescantime * 10 / 0.625), // Time = N * 0.625 msec @@ -275,7 +273,7 @@ void start_BLEscan(void) { ESP_ERROR_CHECK(esp_bluedroid_init()); ESP_ERROR_CHECK(esp_bluedroid_enable()); // Register callback function for capturing bluetooth packets - ESP_ERROR_CHECK(register_ble_callback(true)); + ESP_ERROR_CHECK(register_ble_callback(false)); ESP_LOGI(TAG, "Bluetooth scanner started"); #endif // BLECOUNTER } else { @@ -288,7 +286,7 @@ void start_BLEscan(void) { void stop_BLEscan(void) { #if (BLECOUNTER) ESP_LOGI(TAG, "Shutting down bluetooth scanner ..."); - ESP_ERROR_CHECK(register_ble_callback(false)); // unregister capture function + ESP_ERROR_CHECK(register_ble_callback(true)); // unregister capture function ESP_LOGD(TAG, "bluedroid disable..."); ESP_ERROR_CHECK(esp_bluedroid_disable()); ESP_LOGD(TAG, "bluedroid deinit..."); From 889e88d6712bf9eb97eb600dbb630dbce1754dc1 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 18 Dec 2020 19:07:12 +0100 Subject: [PATCH 037/104] cumulate uptime after deep sleep --- include/cyclic.h | 1 + src/cyclic.cpp | 8 +++++++- src/reset.cpp | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/cyclic.h b/include/cyclic.h index 6bd9b2a7..10509f1b 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -15,6 +15,7 @@ extern Ticker cyclicTimer; void setCyclicIRQ(void); void doHousekeeping(void); +uint64_t _uptime(uint64_t diff); uint64_t uptime(void); void reset_counters(void); uint32_t getFreeRAM(); diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 2799b918..7e12db65 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -136,7 +136,13 @@ void doHousekeeping() { } // doHousekeeping() -uint64_t uptime() { return millis(); } +uint64_t _uptime(uint64_t diff) { + static uint64_t offset = 0; + offset += diff; + return millis() + offset; +} + +uint64_t uptime(void){return _uptime(0);}; uint32_t getFreeRAM() { #ifndef BOARD_HAS_PSRAM diff --git a/src/reset.cpp b/src/reset.cpp index f740d002..40fd9772 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -49,6 +49,7 @@ void do_after_reset(void) { (sleep_stop_time.tv_sec - RTC_sleep_start_time.tv_sec) * 1000 + (sleep_stop_time.tv_usec - RTC_sleep_start_time.tv_usec) / 1000; ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); + _uptime(sleep_time_ms); // increment uptime RTC_runmode = RUNMODE_WAKEUP; break; From fef0a86c4a0d2653114e589f0c344a20d62c9411 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 19 Dec 2020 12:26:27 +0100 Subject: [PATCH 038/104] readme.md: added sleep mode --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 329310e4..2696087d 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,10 @@ Output of sensor and peripheral data is internally switched by a bitmask registe *) GPS data can also be combined with payload on port 1, *#define GPSPORT 1* in paxcounter.conf to enable +# Power saving mode + +Paxcounter supports a battery friendly power saving mode. In this mode the device enters deep sleep, after all data is polled from all sensors and the dataset is completeley sent through all user configured channels (LORAWAN / SPI / MQTT). Set *#define SLEEPCYCLE* in paxcounter.conf to enable power saving mode and to specify the duration of a sleep cycle. Power consumption in deep sleep mode depends on your hardware, i.e. if on board peripherals can be switched off or set to a chip specific sleep mode either by MCU or by power management unit (PMU) as found on TTGO T-BEAM v1.0/V1.1. See *power.cpp* for power management, and *reset.cpp* for sleep and wakeup logic. + # Time sync Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). Bonus: If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the enclosed [**Timeserver code**](/src/Timeserver/Nodered-Timeserver.json). Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using. From 2d486e5edef2d3d61a458bb3a80ab2ab75a75767 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 19 Dec 2020 12:27:07 +0100 Subject: [PATCH 039/104] persist uptime during deep sleep --- include/cyclic.h | 1 - include/reset.h | 2 ++ src/cyclic.cpp | 10 +--------- src/reset.cpp | 6 ++++-- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/include/cyclic.h b/include/cyclic.h index 10509f1b..6bd9b2a7 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -15,7 +15,6 @@ extern Ticker cyclicTimer; void setCyclicIRQ(void); void doHousekeeping(void); -uint64_t _uptime(uint64_t diff); uint64_t uptime(void); void reset_counters(void); uint32_t getFreeRAM(); diff --git a/include/reset.h b/include/reset.h index 69735a77..f540ff62 100644 --- a/include/reset.h +++ b/include/reset.h @@ -13,4 +13,6 @@ void do_reset(bool warmstart); void do_after_reset(void); void enter_deepsleep(const uint64_t wakeup_sec, const gpio_num_t wakeup_gpio); +extern RTC_DATA_ATTR unsigned long RTC_millis; + #endif // _RESET_H \ No newline at end of file diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 7e12db65..b6dd1120 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -20,8 +20,6 @@ void setCyclicIRQ() { // do all housekeeping void doHousekeeping() { - // update uptime counter - uptime(); // check if update mode trigger switch was set if (RTC_runmode == RUNMODE_UPDATE) { // check battery status if we can before doing ota @@ -136,13 +134,7 @@ void doHousekeeping() { } // doHousekeeping() -uint64_t _uptime(uint64_t diff) { - static uint64_t offset = 0; - offset += diff; - return millis() + offset; -} - -uint64_t uptime(void){return _uptime(0);}; +uint64_t uptime() { return (RTC_millis + millis()); } uint32_t getFreeRAM() { #ifndef BOARD_HAS_PSRAM diff --git a/src/reset.cpp b/src/reset.cpp index 40fd9772..ade00195 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -11,6 +11,7 @@ static const char TAG[] = __FILE__; // variables keep its values after a wakeup from sleep RTC_DATA_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; RTC_DATA_ATTR struct timeval RTC_sleep_start_time; +RTC_DATA_ATTR unsigned long RTC_millis = 0; timeval sleep_stop_time; const char *runmode[5] = {"powercycle", "normal", "wakeup", "update", "sleep"}; @@ -49,7 +50,7 @@ void do_after_reset(void) { (sleep_stop_time.tv_sec - RTC_sleep_start_time.tv_sec) * 1000 + (sleep_stop_time.tv_usec - RTC_sleep_start_time.tv_usec) / 1000; ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); - _uptime(sleep_time_ms); // increment uptime + RTC_millis += sleep_time_ms; // increment system monotonic time RTC_runmode = RUNMODE_WAKEUP; break; @@ -180,8 +181,9 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW); } - // time stamp sleep start time. Deep sleep. + // time stamp sleep start time and save system monotonic time. Deep sleep. gettimeofday(&RTC_sleep_start_time, NULL); + RTC_millis = millis(); ESP_LOGI(TAG, "Going to sleep, good bye."); esp_deep_sleep_start(); From 85529920d53505025c1cc71f835529f5791db462 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 19 Dec 2020 12:36:09 +0100 Subject: [PATCH 040/104] ttgobeam10.h: set blue LED off in sleep --- src/hal/ttgobeam10.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h index 568fda1a..f3b075c0 100644 --- a/src/hal/ttgobeam10.h +++ b/src/hal/ttgobeam10.h @@ -46,7 +46,7 @@ Reset -> reset device // possible values: // AXP20X_LED_OFF / AXP20X_LED_LOW_LEVEL (means LED ON) / AXP20X_LED_BLINK_1HZ / AXP20X_LED_BLINK_4HZ #define PMU_LED_RUN_MODE AXP20X_LED_LOW_LEVEL -#define PMU_LED_SLEEP_MODE AXP20X_LED_BLINK_1HZ +#define PMU_LED_SLEEP_MODE AXP20X_LED_OFF // GPS settings #define HAS_GPS 1 // use on board GPS From fdfd10b227dd11f15ff063c28d307c25dc60b105 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 19 Dec 2020 22:30:34 +0100 Subject: [PATCH 041/104] bugfix persist uptime after deep sleep --- include/cyclic.h | 1 - include/reset.h | 3 +-- src/cyclic.cpp | 2 -- src/reset.cpp | 6 ++++-- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/include/cyclic.h b/include/cyclic.h index 6bd9b2a7..8c7319be 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -15,7 +15,6 @@ extern Ticker cyclicTimer; void setCyclicIRQ(void); void doHousekeeping(void); -uint64_t uptime(void); void reset_counters(void); uint32_t getFreeRAM(); diff --git a/include/reset.h b/include/reset.h index f540ff62..0ce84c78 100644 --- a/include/reset.h +++ b/include/reset.h @@ -12,7 +12,6 @@ void do_reset(bool warmstart); void do_after_reset(void); void enter_deepsleep(const uint64_t wakeup_sec, const gpio_num_t wakeup_gpio); - -extern RTC_DATA_ATTR unsigned long RTC_millis; +uint64_t uptime(void); #endif // _RESET_H \ No newline at end of file diff --git a/src/cyclic.cpp b/src/cyclic.cpp index b6dd1120..a927a21e 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -134,8 +134,6 @@ void doHousekeeping() { } // doHousekeeping() -uint64_t uptime() { return (RTC_millis + millis()); } - uint32_t getFreeRAM() { #ifndef BOARD_HAS_PSRAM return ESP.getFreeHeap(); diff --git a/src/reset.cpp b/src/reset.cpp index ade00195..addeb36f 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -183,11 +183,13 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, // time stamp sleep start time and save system monotonic time. Deep sleep. gettimeofday(&RTC_sleep_start_time, NULL); - RTC_millis = millis(); + RTC_millis += millis(); ESP_LOGI(TAG, "Going to sleep, good bye."); esp_deep_sleep_start(); Error: ESP_LOGE(TAG, "Can't go to sleep. Resetting."); do_reset(true); -} \ No newline at end of file +} + +uint64_t uptime() { return (RTC_millis + millis()); } \ No newline at end of file From 62132030b7e9462818d56057117bacb3b3329719 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 21 Dec 2020 19:35:21 +0100 Subject: [PATCH 042/104] bugfix in wifi on/off logic --- src/reset.cpp | 23 ++++++++++++----------- src/wifiscan.cpp | 18 ++++++++---------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index addeb36f..0572dabd 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -89,8 +89,18 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, RTC_runmode = RUNMODE_SLEEP; - // stop further enqueuing of senddata + // switch off radio +#if (WIFICOUNTER) + switch_wifi_sniffer(0); +#endif +#if (BLECOUNTER) + stop_BLEscan(); + btStop(); +#endif + + // stop further enqueuing of senddata and MAC processing sendTimer.detach(); + vTaskDelete(macProcessTask); // halt interrupts accessing i2c bus mask_user_IRQ(); @@ -140,16 +150,7 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, if (i == 0) goto Error; -// switch off radio -#if (WIFICOUNTER) - switch_wifi_sniffer(0); -#endif -#if (BLECOUNTER) - stop_BLEscan(); - btStop(); -#endif - - // save LMIC state to RTC RAM + // save LMIC state to RTC RAM #if (HAS_LORA) SaveLMICToRTC(wakeup_sec); #endif // (HAS_LORA) diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index edcafe13..ada8255a 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -70,11 +70,9 @@ void wifi_sniffer_init(void) { ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // no modem power saving - ESP_ERROR_CHECK( esp_wifi_set_promiscuous_filter(&wifi_filter)); // set frame filter ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler)); - ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); // now switch on monitor mode // setup wifi channel hopping timer WifiChanTimer = @@ -82,24 +80,24 @@ void wifi_sniffer_init(void) { (cfg.wifichancycle > 0) ? pdMS_TO_TICKS(cfg.wifichancycle) : pdMS_TO_TICKS(50), pdTRUE, (void *)0, switchWifiChannel); - // start timer - if (cfg.wifichancycle > 0) - xTimerStart(WifiChanTimer, (TickType_t)0); } void switch_wifi_sniffer(uint8_t state) { if (state) { - // switch wifi sniffer on + // start sniffer ESP_ERROR_CHECK(esp_wifi_start()); - esp_wifi_set_promiscuous(true); - esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); + ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); + ESP_ERROR_CHECK( + esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE)); + // start channel hopping timer if (cfg.wifichancycle > 0) xTimerStart(WifiChanTimer, (TickType_t)0); } else { - // switch wifi sniffer off + // start channel hopping timer if (xTimerIsTimerActive(WifiChanTimer) != pdFALSE) xTimerStop(WifiChanTimer, (TickType_t)0); - esp_wifi_set_promiscuous(false); + // stop sniffer + ESP_ERROR_CHECK(esp_wifi_set_promiscuous(false)); ESP_ERROR_CHECK(esp_wifi_stop()); } } \ No newline at end of file From 5edb904d7cfb541e15bef7acd7dd06b63aa07a3b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 21 Dec 2020 19:41:25 +0100 Subject: [PATCH 043/104] remove all ESP_ERROR_CHECK(x) --- src/blecsan.cpp | 50 +++++++++++++++++++++++++----------------------- src/lorawan.cpp | 2 +- src/main.cpp | 6 +++--- src/power.cpp | 10 +++++----- src/spislave.cpp | 3 +-- src/wifiscan.cpp | 27 ++++++++++++-------------- 6 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/blecsan.cpp b/src/blecsan.cpp index 5deb6004..33d303dc 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -128,7 +128,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, switch (event) { case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: // restart scan - ESP_ERROR_CHECK(esp_ble_gap_start_scanning(BLESCANTIME)); + esp_ble_gap_start_scanning(BLESCANTIME); break; case ESP_GAP_BLE_SCAN_RESULT_EVT: @@ -136,7 +136,7 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, if (p->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_CMPL_EVT) // Inquiry complete, scan is done { // restart scan - ESP_ERROR_CHECK(esp_ble_gap_start_scanning(BLESCANTIME)); + esp_ble_gap_start_scanning(BLESCANTIME); return; } @@ -219,8 +219,8 @@ esp_err_t register_ble_callback(bool unregister = false) { if (unregister) { ESP_LOGI(TAG, "Unregister GAP callback..."); - ESP_ERROR_CHECK(esp_ble_gap_stop_scanning()); - ESP_ERROR_CHECK(esp_ble_gap_register_callback(NULL)); + esp_ble_gap_stop_scanning(); + esp_ble_gap_register_callback(NULL); } @@ -230,23 +230,25 @@ esp_err_t register_ble_callback(bool unregister = false) { // This function is called when gap event occurs, such as scan result. // register the scan callback function to the gap module - ESP_ERROR_CHECK(esp_ble_gap_register_callback(&gap_callback_handler)); + esp_ble_gap_register_callback(&gap_callback_handler); static esp_ble_scan_params_t ble_scan_params = { .scan_type = BLE_SCAN_TYPE_PASSIVE, .own_addr_type = BLE_ADDR_TYPE_RANDOM, .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, -/* - #if (MACFILTER) - .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, - // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used for broadcasting - // data in broadcast applications (e.g., Beacons), so we don't want them in - // macfilter mode - #else - .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, - #endif -*/ + /* + #if (MACFILTER) + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_WLIST_PRA_DIR, + // ADV_IND, ADV_NONCONN_IND, ADV_SCAN_IND packets are used + for broadcasting + // data in broadcast applications (e.g., Beacons), so we + don't want them in + // macfilter mode + #else + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + #endif + */ .scan_interval = (uint16_t)(cfg.blescantime * 10 / 0.625), // Time = N * 0.625 msec @@ -257,7 +259,7 @@ esp_err_t register_ble_callback(bool unregister = false) { ESP_LOGI(TAG, "Set GAP scan parameters"); // This function is called to set scan parameters. - ESP_ERROR_CHECK(esp_ble_gap_set_scan_params(&ble_scan_params)); + esp_ble_gap_set_scan_params(&ble_scan_params); } return ESP_OK; @@ -269,11 +271,11 @@ void start_BLEscan(void) { ESP_LOGI(TAG, "Initializing bluetooth scanner ..."); // Initialize BT controller to allocate task and other resource. if (btStart()) { // enable bt_controller - ESP_ERROR_CHECK(esp_coex_preference_set(ESP_COEX_PREFER_BT)); - ESP_ERROR_CHECK(esp_bluedroid_init()); - ESP_ERROR_CHECK(esp_bluedroid_enable()); + esp_coex_preference_set(ESP_COEX_PREFER_BT); + esp_bluedroid_init(); + esp_bluedroid_enable(); // Register callback function for capturing bluetooth packets - ESP_ERROR_CHECK(register_ble_callback(false)); + register_ble_callback(false); ESP_LOGI(TAG, "Bluetooth scanner started"); #endif // BLECOUNTER } else { @@ -286,16 +288,16 @@ void start_BLEscan(void) { void stop_BLEscan(void) { #if (BLECOUNTER) ESP_LOGI(TAG, "Shutting down bluetooth scanner ..."); - ESP_ERROR_CHECK(register_ble_callback(true)); // unregister capture function + register_ble_callback(true); // unregister capture function ESP_LOGD(TAG, "bluedroid disable..."); - ESP_ERROR_CHECK(esp_bluedroid_disable()); + esp_bluedroid_disable(); ESP_LOGD(TAG, "bluedroid deinit..."); - ESP_ERROR_CHECK(esp_bluedroid_deinit()); + esp_bluedroid_deinit(); if (!btStop()) { // disable bt_controller ESP_LOGE(TAG, "Bluetooth controller stop failed. Resetting device"); do_reset(true); } - ESP_ERROR_CHECK(esp_coex_preference_set(ESP_COEX_PREFER_WIFI)); + esp_coex_preference_set(ESP_COEX_PREFER_WIFI); ESP_LOGI(TAG, "Bluetooth scanner stopped"); #endif // BLECOUNTER } // stop_BLEscan diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 23d3f612..aa0e9ccd 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -106,7 +106,7 @@ breaking change // DevEUI generator using devices's MAC address void gen_lora_deveui(uint8_t *pdeveui) { uint8_t *p = pdeveui, dmac[6]; - ESP_ERROR_CHECK(esp_efuse_mac_get_default(dmac)); + esp_efuse_mac_get_default(dmac); // deveui is LSB, we reverse it so TTN DEVEUI display // will remain the same as MAC address // MAC is 6 bytes, devEUI 8, set middle 2 ones diff --git a/src/main.cpp b/src/main.cpp index b92a5dba..2ce13018 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -304,9 +304,9 @@ void setup() { #else // remove bluetooth stack to gain more free memory btStop(); - ESP_ERROR_CHECK(esp_bt_mem_release(ESP_BT_MODE_BTDM)); - ESP_ERROR_CHECK(esp_coex_preference_set( - ESP_COEX_PREFER_WIFI)); // configure Wifi/BT coexist lib + esp_bt_mem_release(ESP_BT_MODE_BTDM); + esp_coex_preference_set( + ESP_COEX_PREFER_WIFI); // configure Wifi/BT coexist lib #endif // initialize gps diff --git a/src/power.cpp b/src/power.cpp index 3dee573c..7d49f5be 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -171,11 +171,11 @@ void calibrate_voltage(void) { #ifdef BAT_MEASURE_ADC // configure ADC #ifndef BAT_MEASURE_ADC_UNIT // ADC1 - ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12)); - ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, atten)); + adc1_config_width(ADC_WIDTH_BIT_12); + adc1_config_channel_atten(adc_channel, atten); #else // ADC2 - // ESP_ERROR_CHECK(adc2_config_width(ADC_WIDTH_BIT_12)); - ESP_ERROR_CHECK(adc2_config_channel_atten(adc_channel, atten)); + // adc2_config_width(ADC_WIDTH_BIT_12); + adc2_config_channel_atten(adc_channel, atten); #endif // calibrate ADC esp_adc_cal_value_t val_type = esp_adc_cal_characterize( @@ -210,7 +210,7 @@ uint16_t read_voltage(void) { #else // ADC2 int adc_buf = 0; for (int i = 0; i < NO_OF_SAMPLES; i++) { - ESP_ERROR_CHECK(adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf)); + adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf); adc_reading += adc_buf; } #endif // BAT_MEASURE_ADC_UNIT diff --git a/src/spislave.cpp b/src/spislave.cpp index bee8497e..57b36614 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -91,8 +91,7 @@ void spi_slave_task(void *param) { // wait until spi master clocks out the data, and read results in rx buffer ESP_LOGI(TAG, "Prepared SPI transaction for %zu byte(s)", transaction_size); ESP_LOG_BUFFER_HEXDUMP(TAG, txbuf, transaction_size, ESP_LOG_DEBUG); - ESP_ERROR_CHECK_WITHOUT_ABORT( - spi_slave_transmit(HSPI_HOST, &spi_transaction, portMAX_DELAY)); + spi_slave_transmit(HSPI_HOST, &spi_transaction, portMAX_DELAY); ESP_LOG_BUFFER_HEXDUMP(TAG, rxbuf, transaction_size, ESP_LOG_DEBUG); ESP_LOGI(TAG, "Transaction finished with size %zu bits", spi_transaction.trans_len); diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index ada8255a..c876803c 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -64,15 +64,13 @@ void wifi_sniffer_init(void) { WIFI_PROMIS_FILTER_MASK_MGMT | WIFI_PROMIS_FILTER_MASK_DATA}; - ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg)); // start Wifi task - ESP_ERROR_CHECK( - esp_wifi_set_country(&wifi_country)); // set locales for RF and channels - ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); - ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // no modem power saving - ESP_ERROR_CHECK( - esp_wifi_set_promiscuous_filter(&wifi_filter)); // set frame filter - ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler)); + esp_wifi_init(&wifi_cfg); // start Wifi task + esp_wifi_set_country(&wifi_country); // set locales for RF and channels + esp_wifi_set_storage(WIFI_STORAGE_RAM); + esp_wifi_set_mode(WIFI_MODE_NULL); + esp_wifi_set_ps(WIFI_PS_NONE); // no modem power saving + esp_wifi_set_promiscuous_filter(&wifi_filter); // set frame filter + esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler); // setup wifi channel hopping timer WifiChanTimer = @@ -85,10 +83,9 @@ void wifi_sniffer_init(void) { void switch_wifi_sniffer(uint8_t state) { if (state) { // start sniffer - ESP_ERROR_CHECK(esp_wifi_start()); - ESP_ERROR_CHECK(esp_wifi_set_promiscuous(true)); - ESP_ERROR_CHECK( - esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE)); + esp_wifi_start(); + esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(WIFI_CHANNEL_MIN, WIFI_SECOND_CHAN_NONE); // start channel hopping timer if (cfg.wifichancycle > 0) xTimerStart(WifiChanTimer, (TickType_t)0); @@ -97,7 +94,7 @@ void switch_wifi_sniffer(uint8_t state) { if (xTimerIsTimerActive(WifiChanTimer) != pdFALSE) xTimerStop(WifiChanTimer, (TickType_t)0); // stop sniffer - ESP_ERROR_CHECK(esp_wifi_set_promiscuous(false)); - ESP_ERROR_CHECK(esp_wifi_stop()); + esp_wifi_set_promiscuous(false); + esp_wifi_stop(); } } \ No newline at end of file From f29f2958267a6ed625121c2dbbf892f4928d1c00 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 21 Dec 2020 22:07:24 +0100 Subject: [PATCH 044/104] change send data order to counts first --- README.md | 8 ++++---- include/globals.h | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2696087d..05de35d7 100644 --- a/README.md +++ b/README.md @@ -490,15 +490,15 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0x14 set payload mask byte 1 = sensor data payload mask (0..255, meaning of bits see below) - 0x01 = GPS_DATA + 0x01 = COUNT_DATA 0x02 = ALARM_DATA 0x04 = MEMS_DATA - 0x08 = COUNT_DATA (default) - 0x10 = SENSOR_1_DATA (ENS-COUNTS) + 0x08 = GPS_DATA + 0x10 = SENSOR_1_DATA (also ENS counter) 0x20 = SENSOR_2_DATA 0x40 = SENSOR_3_DATA 0x80 = BATT_DATA - bytes can be combined eg COUNT_DATA ;SENSOR_1_DATA ;BATT_DATA: `0x08 | 0x10 |0x80 = 0x98` + bytes can be combined eg COUNT_DATA + SENSOR_1_DATA + BATT_DATA: `0x01 | 0x10 | 0x80 = 0x91` 0x15 set BME data on/off diff --git a/include/globals.h b/include/globals.h index 9c771b50..fecfbf32 100644 --- a/include/globals.h +++ b/include/globals.h @@ -19,10 +19,10 @@ #include // bits in payloadmask for filtering payload data -#define GPS_DATA (0x01) +#define COUNT_DATA (0x01) #define ALARM_DATA (0x02) #define MEMS_DATA (0x04) -#define COUNT_DATA (0x08) +#define GPS_DATA (0x08) #define SENSOR1_DATA (0x10) #define SENSOR2_DATA (0x20) #define SENSOR3_DATA (0x40) From 069c5ec3ecebc3daf05b5d2a71a6a411992cdd7b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 21 Dec 2020 22:10:58 +0100 Subject: [PATCH 045/104] readme.md update counter order --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 05de35d7..54335bbd 100644 --- a/README.md +++ b/README.md @@ -183,16 +183,16 @@ Output of sensor and peripheral data is internally switched by a bitmask registe | Bit | Sensordata | Default | --- | ------------- | ------- -| 0 | GPS* | on +| 0 | Paxcounter | on | 1 | Beacon alarm | on | 2 | BME280/680 | on -| 3 | Paxcounter | on +| 3 | GPS* | on | 4 | User sensor 1 | on | 5 | User sensor 2 | on | 6 | User sensor 3 | on | 7 | Batterylevel | off -*) GPS data can also be combined with payload on port 1, *#define GPSPORT 1* in paxcounter.conf to enable +*) GPS data can also be combined with paxcounter payload on port 1, *#define GPSPORT 1* in paxcounter.conf to enable # Power saving mode From a04e6440d46bd01cface2f64dc9fa0d795ac0374 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 21 Dec 2020 22:11:41 +0100 Subject: [PATCH 046/104] millis() overflow logic reworked --- include/reset.h | 2 +- src/bmesensor.cpp | 4 ++-- src/led.cpp | 2 +- src/ota.cpp | 2 +- src/rcommand.cpp | 4 ++-- src/reset.cpp | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/reset.h b/include/reset.h index 0ce84c78..af9d6c50 100644 --- a/include/reset.h +++ b/include/reset.h @@ -12,6 +12,6 @@ void do_reset(bool warmstart); void do_after_reset(void); void enter_deepsleep(const uint64_t wakeup_sec, const gpio_num_t wakeup_gpio); -uint64_t uptime(void); +unsigned long long uptime(void); #endif // _RESET_H \ No newline at end of file diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 4a365d90..56924d4a 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -26,7 +26,6 @@ bsec_virtual_sensor_t sensorList[10] = { }; uint8_t bsecstate_buffer[BSEC_MAX_STATE_BLOB_SIZE] = {0}; -uint16_t stateUpdateCounter = 0; Bsec iaqSensor; @@ -218,6 +217,7 @@ void loadState(void) { void updateState(void) { bool update = false; + static uint16_t stateUpdateCounter = 0; if (stateUpdateCounter == 0) { // first state update when IAQ accuracy is >= 1 @@ -228,7 +228,7 @@ void updateState(void) { } else { /* Update every STATE_SAVE_PERIOD minutes */ - if ((stateUpdateCounter * STATE_SAVE_PERIOD) < millis()) { + if ((long)(millis() - stateUpdateCounter * STATE_SAVE_PERIOD) >= 0) { update = true; stateUpdateCounter++; } diff --git a/src/led.cpp b/src/led.cpp index e74e92c6..b0b302d6 100644 --- a/src/led.cpp +++ b/src/led.cpp @@ -146,7 +146,7 @@ void ledLoop(void *parameter) { // management if (LEDBlinkStarted && LEDBlinkDuration) { // Custom blink is finished, let this order, avoid millis() overflow - if ((millis() - LEDBlinkStarted) >= LEDBlinkDuration) { + if ((long)(millis() - LEDBlinkStarted) >= LEDBlinkDuration) { // Led becomes off, and stop blink LEDState = LED_OFF; LEDBlinkStarted = 0; diff --git a/src/ota.cpp b/src/ota.cpp index c256559e..6b94a193 100644 --- a/src/ota.cpp +++ b/src/ota.cpp @@ -180,7 +180,7 @@ int do_ota_update() { unsigned long timeout = millis(); while (client.available() == 0) { - if ((millis() - timeout) > (RESPONSE_TIMEOUT_MS)) { + if ((long)(millis() - timeout) > (RESPONSE_TIMEOUT_MS)) { ESP_LOGI(TAG, "Client timeout"); ota_display(3, " E", "client timeout"); goto abort; diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 0759dc79..afea9eb4 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -298,8 +298,8 @@ void get_config(uint8_t val[]) { void get_status(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get device status"); payload.reset(); - payload.addStatus(read_voltage(), uptime() / 1000, temperatureRead(), - getFreeRAM(), rtc_get_reset_reason(0), + payload.addStatus(read_voltage(), (uint64_t)(uptime() / 1000ULL), + temperatureRead(), getFreeRAM(), rtc_get_reset_reason(0), rtc_get_reset_reason(1)); SendPayload(STATUSPORT); }; diff --git a/src/reset.cpp b/src/reset.cpp index 0572dabd..18412909 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -11,7 +11,7 @@ static const char TAG[] = __FILE__; // variables keep its values after a wakeup from sleep RTC_DATA_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; RTC_DATA_ATTR struct timeval RTC_sleep_start_time; -RTC_DATA_ATTR unsigned long RTC_millis = 0; +RTC_DATA_ATTR unsigned long long RTC_millis = 0; timeval sleep_stop_time; const char *runmode[5] = {"powercycle", "normal", "wakeup", "update", "sleep"}; @@ -193,4 +193,4 @@ Error: do_reset(true); } -uint64_t uptime() { return (RTC_millis + millis()); } \ No newline at end of file +unsigned long long uptime() { return (RTC_millis + millis()); } \ No newline at end of file From 2163c539c9614baddd49e1a8b7907d993fb04ba2 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 22 Dec 2020 15:20:32 +0100 Subject: [PATCH 047/104] increase task stack size of macprocessor --- src/macsniff.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 2f5446f2..6fefc07c 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -54,7 +54,7 @@ esp_err_t macQueueInit() { xTaskCreatePinnedToCore(mac_process, // task function "mac_process", // name of task - 2048, // stack size of task + 3072, // stack size of task (void *)1, // parameter of the task 1, // priority of the task &macProcessTask, // task handle From 46d32f8cfefe95c39ecbee40e69dd47103efa37a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 22 Dec 2020 16:17:49 +0100 Subject: [PATCH 048/104] AXP192 longpress to switch off --- src/power.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/power.cpp b/src/power.cpp index 7d49f5be..eb1586ee 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -131,7 +131,7 @@ void AXP192_init(void) { pmu.setDCDC1Voltage(3300); // for external OLED display pmu.setLDO2Voltage(3300); // LORA VDD 3v3 pmu.setLDO3Voltage(3300); // GPS VDD 3v3 - pmu.setTimeOutShutdown(false); // no automatic shutdown + pmu.setTimeOutShutdown(true); // shutdown by longpress pmu.setTSmode(AXP_TS_PIN_MODE_DISABLE); // TS pin mode off to save power // switch ADCs on From 457d46529bd76304ba29a0c20fd7e70c420c5ab9 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 22 Dec 2020 19:13:46 +0100 Subject: [PATCH 049/104] enter_deepsleep: stop senddata early --- src/reset.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index 18412909..3a07d227 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -89,6 +89,9 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, RTC_runmode = RUNMODE_SLEEP; + // stop further enqueuing of senddata and MAC processing + sendTimer.detach(); + // switch off radio #if (WIFICOUNTER) switch_wifi_sniffer(0); @@ -98,8 +101,7 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, btStop(); #endif - // stop further enqueuing of senddata and MAC processing - sendTimer.detach(); + // stop MAC processing vTaskDelete(macProcessTask); // halt interrupts accessing i2c bus From f93594e9647625a81a2abfbbf2e76c67ed05904a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 22 Dec 2020 20:06:16 +0100 Subject: [PATCH 050/104] cleanup some compiler warnings --- src/blecsan.cpp | 4 ++-- src/bmesensor.cpp | 2 +- src/rcommand.cpp | 2 +- src/timesync.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/blecsan.cpp b/src/blecsan.cpp index 33d303dc..8b8a8207 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -253,8 +253,8 @@ esp_err_t register_ble_callback(bool unregister = false) { .scan_interval = (uint16_t)(cfg.blescantime * 10 / 0.625), // Time = N * 0.625 msec .scan_window = - (uint16_t)(BLESCANWINDOW / 0.625) // Time = N * 0.625 msec - }; + (uint16_t)(BLESCANWINDOW / 0.625), // Time = N * 0.625 msec + .scan_duplicate = BLE_SCAN_DUPLICATE_ENABLE}; ESP_LOGI(TAG, "Set GAP scan parameters"); diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 56924d4a..9ba31fcf 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -5,7 +5,7 @@ // Local logging tag static const char TAG[] = __FILE__; -bmeStatus_t bme_status = {0}; +bmeStatus_t bme_status = {0, 0, 0, 0, 0, 0, 0, 0}; Ticker bmecycler; diff --git a/src/rcommand.cpp b/src/rcommand.cpp index afea9eb4..d69bb0cc 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -267,7 +267,7 @@ void set_macfilter(uint8_t val[]) { void set_rgblum(uint8_t val[]) { // Avoid wrong parameters - cfg.rgblum = (val[0] >= 0 && val[0] <= 100) ? (uint8_t)val[0] : RGBLUMINOSITY; + cfg.rgblum = (val[0] <= 100) ? (uint8_t)val[0] : RGBLUMINOSITY; ESP_LOGI(TAG, "Remote command: set RGB Led luminosity %d", cfg.rgblum); }; diff --git a/src/timesync.cpp b/src/timesync.cpp index c97da655..55a0d82f 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -82,7 +82,7 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { } // collect timestamp samples in timestamp array - for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { + for (int8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { // send timesync request #if (TIME_SYNC_LORASERVER) // ask user's timeserver (for LoRAWAN < 1.0.3) From 1ae49f8263877ca12c241453c5d813f9b1fca9db Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 23 Dec 2020 16:30:25 +0100 Subject: [PATCH 051/104] add spi deinit for sleep --- include/spislave.h | 4 ++-- src/spislave.cpp | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/spislave.h b/include/spislave.h index e4b71f02..78b7a737 100644 --- a/include/spislave.h +++ b/include/spislave.h @@ -27,10 +27,10 @@ licenses. Refer to LICENSE.txt file in repository for more details. #include "globals.h" #include "rcommand.h" -esp_err_t spi_init(); - extern TaskHandle_t spiTask; +esp_err_t spi_init(); +void spi_deinit(void); void spi_enqueuedata(MessageBuffer_t *message); uint32_t spi_queuewaiting(void); void spi_queuereset(void); diff --git a/src/spislave.cpp b/src/spislave.cpp index 57b36614..d0778384 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -106,7 +106,9 @@ void spi_slave_task(void *param) { } } -esp_err_t spi_init() { +void spi_deinit(void) { vTaskDelete(spiTask); } + +esp_err_t spi_init(void) { _ASSERT(SEND_QUEUE_SIZE > 0); SPISendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t)); if (SPISendQueue == 0) { From 1950650bfb9c35765dd00ad122b8fa9f23d1851c Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 23 Dec 2020 16:31:06 +0100 Subject: [PATCH 052/104] add deinit functions to enter_deepsleep --- src/reset.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index 3a07d227..472ed02c 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -134,12 +134,12 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, // shutdown MQTT safely #ifdef HAS_MQTT -// to come + mqtt_deinit(); #endif // shutdown SPI safely #ifdef HAS_SPI -// to come + spi_deinit(); #endif // wait until rcommands are all done From a30eab54f79dc2f3368bcbc034d74b3ac352ec8f Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 23 Dec 2020 16:31:31 +0100 Subject: [PATCH 053/104] mqttclient reworked (experimental) --- include/irqhandler.h | 2 -- include/mqttclient.h | 13 +++++-------- src/irqhandler.cpp | 8 -------- src/main.cpp | 1 - src/mqttclient.cpp | 45 ++++++++++++++++++++------------------------ 5 files changed, 25 insertions(+), 44 deletions(-) diff --git a/include/irqhandler.h b/include/irqhandler.h index 712e5e41..958acc4e 100644 --- a/include/irqhandler.h +++ b/include/irqhandler.h @@ -11,7 +11,6 @@ #define BME_IRQ 0x080 #define MATRIX_DISPLAY_IRQ 0x100 #define PMU_IRQ 0x200 -#define MQTT_IRQ 0x400 #include "globals.h" #include "button.h" @@ -21,7 +20,6 @@ #include "bmesensor.h" #include "power.h" #include "ledmatrixdisplay.h" -#include "mqttclient.h" void irqHandler(void *pvParameters); void mask_user_IRQ(); diff --git a/include/mqttclient.h b/include/mqttclient.h index 804ee0f3..6e351e2a 100644 --- a/include/mqttclient.h +++ b/include/mqttclient.h @@ -6,17 +6,15 @@ #include #include -#define MQTT_ETHERNET 0 // set to 0 to run on Wifi +#define MQTT_ETHERNET 0 // select PHY: set 0 for Wifi, 1 for ethernet #define MQTT_INTOPIC "paxin" #define MQTT_OUTTOPIC "paxout" #define MQTT_PORT 1883 -#define MQTT_SERVER "broker.shiftr.io" -//#define MQTT_CLIENTNAME "arduino" -#define MQTT_USER "try" -#define MQTT_PASSWD "try" +#define MQTT_SERVER "paxcounter.cloud.shiftr.io" +#define MQTT_USER "public" +#define MQTT_PASSWD "public" #define MQTT_RETRYSEC 20 // retry reconnect every 20 seconds #define MQTT_KEEPALIVE 10 // keep alive interval in seconds -#define MQTT_TIMEOUT 1000 // timeout for all mqtt commands in milliseconds #ifndef MQTT_CLIENTNAME #define MQTT_CLIENTNAME clientId.c_str() @@ -27,13 +25,12 @@ extern TaskHandle_t mqttTask; void mqtt_enqueuedata(MessageBuffer_t *message); uint32_t mqtt_queuewaiting(void); void mqtt_queuereset(void); -void setMqttIRQ(void); -void mqtt_loop(void); void mqtt_client_task(void *param); int mqtt_connect(const char *my_host, const uint16_t my_port); void mqtt_callback(MQTTClient *client, char topic[], char payload[], int length); void NetworkEvent(WiFiEvent_t event); esp_err_t mqtt_init(void); +void mqtt_deinit(void); #endif // _MQTTCLIENT_H \ No newline at end of file diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index 687a6965..244910ad 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -69,14 +69,6 @@ void irqHandler(void *pvParameters) { } #endif -// MQTT loop due? -#if (HAS_MQTT) - if (InterruptStatus & MQTT_IRQ) { - mqtt_loop(); - InterruptStatus &= ~MQTT_IRQ; - } -#endif - // are cyclic tasks due? if (InterruptStatus & CYCLIC_IRQ) { doHousekeeping(); diff --git a/src/main.cpp b/src/main.cpp index 2ce13018..b52ce977 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,7 +68,6 @@ TIMESYNC_IRQ -> setTimeSyncIRQ() CYCLIC_IRQ -> setCyclicIRQ() SENDCYCLE_IRQ -> setSendIRQ() BME_IRQ -> setBMEIRQ() -MQTT_IRQ -> setMqttIRQ() ClockTask (Core 1), see timekeeper.cpp diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 7a1f3af7..867f06db 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -11,13 +11,17 @@ Ticker mqttTimer; WiFiClient netClient; MQTTClient mqttClient; +void mqtt_deinit(void) { + mqttClient.onMessageAdvanced(NULL); + mqttClient.disconnect(); + vTaskDelete(mqttTask); +} + esp_err_t mqtt_init(void) { // setup network connection WiFi.onEvent(NetworkEvent); ETH.begin(); - // WiFi.mode(WIFI_STA); - // WiFi.begin("SSID", "PASSWORD"); // setup mqtt client mqttClient.begin(MQTT_SERVER, MQTT_PORT, netClient); @@ -33,7 +37,6 @@ esp_err_t mqtt_init(void) { SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE); ESP_LOGI(TAG, "Starting MQTTloop..."); - mqttTimer.attach(MQTT_KEEPALIVE, setMqttIRQ); xTaskCreate(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1, &mqttTask); return ESP_OK; @@ -113,16 +116,19 @@ void mqtt_client_task(void *param) { while (1) { - // fetch next or wait for payload to send from queue - // do not delete item from queue until it is transmitted - if (xQueuePeek(MQTTSendQueue, &msg, portMAX_DELAY) != pdTRUE) { - ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); - continue; - } - - // send data to mqtt server, if we are connected if (mqttClient.connected()) { + // check for incoming messages + mqttClient.loop(); + + // fetch next or wait for payload to send from queue + // do not delete item from queue until it is transmitted + // consider mqtt timeout while waiting + if (xQueuePeek(MQTTSendQueue, &msg, + MQTT_KEEPALIVE * 1000 / portTICK_PERIOD_MS) != pdTRUE) + continue; + + // send data to mqtt server char buffer[PAYLOAD_BUFFER_SIZE + 3]; snprintf(buffer, msg.MessageSize + 3, "%u/%s", msg.MessagePort, msg.Message); @@ -131,20 +137,17 @@ void mqtt_client_task(void *param) { ESP_LOGI(TAG, "%d byte(s) sent to MQTT server", msg.MessageSize + 2); // delete sent item from queue xQueueReceive(MQTTSendQueue, &msg, (TickType_t)0); - continue; } else ESP_LOGD(TAG, "Couldn't sent message to MQTT server"); - } else { // attempt to reconnect to MQTT server - ESP_LOGD(TAG, "MQTT client reconnecting..."); - ESP_LOGD(TAG, "MQTT last_error = %d / rc = %d", mqttClient.lastError(), + ESP_LOGD(TAG, "MQTT error = %d / rc = %d", mqttClient.lastError(), mqttClient.returnCode()); + ESP_LOGD(TAG, "MQTT client reconnecting..."); delay(MQTT_RETRYSEC * 1000); mqtt_connect(MQTT_SERVER, MQTT_PORT); } - - } // while(1) + } // while (1) } void mqtt_enqueuedata(MessageBuffer_t *message) { @@ -159,18 +162,10 @@ void mqtt_callback(MQTTClient *client, char topic[], char payload[], rcommand((const uint8_t *)payload, (const uint8_t)length); } -void mqtt_loop(void) { - if (!mqttClient.loop()) - ESP_LOGD(TAG, "MQTT last_error = %d / rc = %d", mqttClient.lastError(), - mqttClient.returnCode()); -} - void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); } uint32_t mqtt_queuewaiting(void) { return uxQueueMessagesWaiting(MQTTSendQueue); } -void setMqttIRQ(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); } - #endif // HAS_MQTT \ No newline at end of file From 71ae40c260da98ab004bbeea6ac490344f853c67 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 24 Dec 2020 16:52:07 +0100 Subject: [PATCH 054/104] code for bitmasks sanitzed --- include/globals.h | 35 +++++++++++++++++++---------------- include/irqhandler.h | 20 ++++++++++---------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/include/globals.h b/include/globals.h index fecfbf32..c8755412 100644 --- a/include/globals.h +++ b/include/globals.h @@ -18,25 +18,28 @@ #include "mallocator.h" #include +#define _bit(b) (1U << (b)) +#define _bitl(b) (1UL << (b)) + // bits in payloadmask for filtering payload data -#define COUNT_DATA (0x01) -#define ALARM_DATA (0x02) -#define MEMS_DATA (0x04) -#define GPS_DATA (0x08) -#define SENSOR1_DATA (0x10) -#define SENSOR2_DATA (0x20) -#define SENSOR3_DATA (0x40) -#define BATT_DATA (0x80) +#define COUNT_DATA _bit(1) +#define ALARM_DATA _bit(2) +#define MEMS_DATA _bit(3) +#define GPS_DATA _bit(4) +#define SENSOR1_DATA _bit(5) +#define SENSOR2_DATA _bit(6) +#define SENSOR3_DATA _bit(7) +#define BATT_DATA _bit(8) // bits in configmask for device runmode control -#define GPS_MODE (0x01) -#define ALARM_MODE (0x02) -#define BEACON_MODE (0x04) -#define UPDATE_MODE (0x08) -#define FILTER_MODE (0x10) -#define ANTENNA_MODE (0x20) -#define BLE_MODE (0x40) -#define SCREEN_MODE (0x80) +#define GPS_MODE _bit(1) +#define ALARM_MODE _bit(2) +#define BEACON_MODE _bit(3) +#define UPDATE_MODE _bit(4) +#define FILTER_MODE _bit(5) +#define ANTENNA_MODE _bit(6) +#define BLE_MODE _bit(7) +#define SCREEN_MODE _bit(8) // length of display buffer for lmic event messages #define LMIC_EVENTMSG_LEN 17 diff --git a/include/irqhandler.h b/include/irqhandler.h index 958acc4e..9e52ccbf 100644 --- a/include/irqhandler.h +++ b/include/irqhandler.h @@ -1,16 +1,16 @@ #ifndef _IRQHANDLER_H #define _IRQHANDLER_H -#define DISPLAY_IRQ 0x001 -#define BUTTON_IRQ 0x002 -#define SENDCYCLE_IRQ 0x004 -#define CYCLIC_IRQ 0x008 -#define TIMESYNC_IRQ 0x010 -#define MASK_IRQ 0x020 -#define UNMASK_IRQ 0x040 -#define BME_IRQ 0x080 -#define MATRIX_DISPLAY_IRQ 0x100 -#define PMU_IRQ 0x200 +#define DISPLAY_IRQ _bitl(1) +#define BUTTON_IRQ _bitl(2) +#define SENDCYCLE_IRQ _bitl(3) +#define CYCLIC_IRQ _bitl(4) +#define TIMESYNC_IRQ _bitl(5) +#define MASK_IRQ _bitl(6) +#define UNMASK_IRQ _bitl(7) +#define BME_IRQ _bitl(8) +#define MATRIX_DISPLAY_IRQ _bitl(9) +#define PMU_IRQ _bitl(10) #include "globals.h" #include "button.h" From 80d46ae001d6f8c55a79400884834568586f20c5 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 24 Dec 2020 16:52:28 +0100 Subject: [PATCH 055/104] AXP192 ecplicit register init --- src/power.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index eb1586ee..bf7b7ebc 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -127,12 +127,22 @@ void AXP192_init(void) { ESP_LOGI(TAG, "AXP192 PMU initialization failed"); else { - // configure AXP192 - pmu.setDCDC1Voltage(3300); // for external OLED display - pmu.setLDO2Voltage(3300); // LORA VDD 3v3 - pmu.setLDO3Voltage(3300); // GPS VDD 3v3 - pmu.setTimeOutShutdown(true); // shutdown by longpress - pmu.setTSmode(AXP_TS_PIN_MODE_DISABLE); // TS pin mode off to save power + // configure voltages + pmu.setDCDC1Voltage(3300); // for external OLED display + pmu.setLDO2Voltage(3300); // LORA VDD 3v3 + pmu.setLDO3Voltage(3300); // GPS VDD 3v3 + + // configure PEK button settings + pmu.setTimeOutShutdown(false); // forced shutdown by PEK enabled + pmu.setShutdownTime( + AXP_POWER_OFF_TIME_65); // 6 sec button press for shutdown + pmu.setlongPressTime( + AXP_LONGPRESS_TIME_1S5); // 1.5 sec button press for long press + pmu.setStartupTime( + AXP192_STARTUP_TIME_1S); // 1 sec button press for startup + + // set battery temperature sensing pin off to save power + pmu.setTSmode(AXP_TS_PIN_MODE_DISABLE); // switch ADCs on pmu.adc1Enable(AXP202_BATT_VOL_ADC1, true); @@ -145,8 +155,7 @@ void AXP192_init(void) { attachInterrupt(digitalPinToInterrupt(PMU_INT), PMUIRQ, FALLING); pmu.enableIRQ(AXP202_VBUS_REMOVED_IRQ | AXP202_VBUS_CONNECT_IRQ | AXP202_BATT_REMOVED_IRQ | AXP202_BATT_CONNECT_IRQ | - AXP202_CHARGING_FINISHED_IRQ | AXP202_PEK_SHORTPRESS_IRQ | - AXP202_PEK_LONGPRESS_IRQ, + AXP202_CHARGING_FINISHED_IRQ | AXP202_PEK_SHORTPRESS_IRQ, 1); pmu.clearIRQ(); #endif // PMU_INT From 476639c731e014f39a039c55a1231592a0fb3818 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 24 Dec 2020 16:54:20 +0100 Subject: [PATCH 056/104] ttgobeam.h comments edited --- src/hal/ttgobeam10.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h index f3b075c0..b014051b 100644 --- a/src/hal/ttgobeam10.h +++ b/src/hal/ttgobeam10.h @@ -14,9 +14,9 @@ pinouts taken from https://github.com/lewisxhe/TTGO-T-Beam /// Button functions: /// Power, short press -> set device on / while device is on: goto sleep -Power, long press -> set device off -User, short press -> flip display page -User, long press -> send LORA message +Power, long press -> set device off +User, short press -> flip display page +User, long press -> send a button message Reset -> reset device */ From 81c444670ec4928054dbf95fb8d6baa2d9a80541 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 24 Dec 2020 18:20:53 +0100 Subject: [PATCH 057/104] power.cpp code sanitizations --- src/power.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index bf7b7ebc..a1fbea58 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -68,10 +68,6 @@ void AXP192_power(pmu_power_t powerlevel) { switch (powerlevel) { case pmu_power_off: - pmu.setChgLEDMode(AXP20X_LED_OFF); - pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); - pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); - pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); pmu.shutdown(); break; @@ -81,18 +77,19 @@ void AXP192_power(pmu_power_t powerlevel) { #else pmu.setChgLEDMode(AXP20X_LED_OFF); #endif - // we don't cut off DCDC1, because then display blocks i2c bus + // we don't cut off DCDC1, because OLED display will then block i2c bus + // pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); // OLED off pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // gps off pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // lora off break; case pmu_power_on: - default: // all rails power on - pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON); // Lora on T-Beam V1.0 - pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON); // Gps on T-Beam V1.0 - pmu.setPowerOutPut(AXP192_DCDC1, AXP202_ON); // OLED on T-Beam v1.0 - pmu.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // unused on T-Beam v1.0 - pmu.setPowerOutPut(AXP192_EXTEN, AXP202_OFF); // unused on T-Beam v1.0 + default: + pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON); // Lora on T-Beam V1.0/1.1 + pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON); // Gps on T-Beam V1.0/1.1 + pmu.setPowerOutPut(AXP192_DCDC1, AXP202_ON); // OLED on T-Beam v1.0/1.1 + pmu.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // unused on T-Beam v1.0/1.1 + pmu.setPowerOutPut(AXP192_EXTEN, AXP202_OFF); // unused on T-Beam v1.0/1.1 #ifdef PMU_LED_RUN_MODE pmu.setChgLEDMode(PMU_LED_RUN_MODE); #else From 6b45669aebd7ec58ce719d411125c76d00a5a443 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 25 Dec 2020 16:45:03 +0100 Subject: [PATCH 058/104] remove useless pinMode from i2c_deinit() --- src/i2c.cpp | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/i2c.cpp b/src/i2c.cpp index dcecc797..226d08c8 100644 --- a/src/i2c.cpp +++ b/src/i2c.cpp @@ -7,12 +7,7 @@ static const char TAG[] = __FILE__; void i2c_init(void) { Wire.begin(MY_DISPLAY_SDA, MY_DISPLAY_SCL, 100000); } -void i2c_deinit(void) { - Wire.~TwoWire(); // shutdown/power off I2C hardware - // configure pins as input to save power, because Wire.end() enables pullups - pinMode(MY_DISPLAY_SDA, INPUT); - pinMode(MY_DISPLAY_SCL, INPUT); -} +void i2c_deinit(void) { Wire.~TwoWire(); } void i2c_scan(void) { @@ -38,10 +33,13 @@ void i2c_scan(void) { BBI2C bbi2c; - const char *szNames[] = {"Unknown","SSD1306","SH1106","VL53L0X","BMP180", "BMP280","BME280", - "MPU-60x0", "MPU-9250", "MCP9808","LSM6DS3", "ADXL345", "ADS1115","MAX44009", - "MAG3110", "CCS811", "HTS221", "LPS25H", "LSM9DS1","LM8330", "DS3231", "LIS3DH", - "LIS3DSH","INA219","SHT3X","HDC1080","MPU6886","BME680", "AXP202", "AXP192", "24AA02XEXX", "DS1307"}; + const char *szNames[] = { + "Unknown", "SSD1306", "SH1106", "VL53L0X", "BMP180", "BMP280", + "BME280", "MPU-60x0", "MPU-9250", "MCP9808", "LSM6DS3", "ADXL345", + "ADS1115", "MAX44009", "MAG3110", "CCS811", "HTS221", "LPS25H", + "LSM9DS1", "LM8330", "DS3231", "LIS3DH", "LIS3DSH", "INA219", + "SHT3X", "HDC1080", "MPU6886", "BME680", "AXP202", "AXP192", + "24AA02XEXX", "DS1307"}; ESP_LOGI(TAG, "Starting I2C bus scan..."); From a3c37e275f62ff6658ad4cefe339ec791533ad3b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 25 Dec 2020 16:54:22 +0100 Subject: [PATCH 059/104] readme.md updated --- README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 54335bbd..79634f3a 100644 --- a/README.md +++ b/README.md @@ -16,15 +16,13 @@ Tutorial (in german language): https://www.heise.de/select/make/2019/1/155109923 # Use case -Paxcounter is a proof-of-concept device for metering passenger flows in realtime. It counts how many mobile devices are around. This gives an estimation how many people are around. Paxcounter detects Wifi and Bluetooth signals in the air, focusing on mobile devices by filtering their MAC adresses. +Paxcounter is an [ESP32](https://www.espressif.com/en/products/socs/esp32) MCU based device for metering passenger flows in realtime. It counts how many mobile devices are around. This gives an estimation how many people are around. Paxcounter detects Wifi and Bluetooth signals in the air, focusing on mobile devices by evaluating their MAC adresses. Intention of this project is to do this without intrusion in privacy: You don't need to track people owned devices, if you just want to count them. Therefore, Paxcounter does not persistenly store MAC adresses and does no kind of fingerprinting the scanned devices. -Data is transferred to a server via a LoRaWAN network, and/or a wired SPI slave interface. It can also be stored on a local SD-card. +Data can either be be stored on a local SD-card, transferred to cloud using LoRaWAN network or MQTT over TCP/IP, or transmitted to a local host using serial (SPI) interface. -You can build this project battery powered and reach a full day uptime with a single 18650 Li-Ion cell. - -This can all be done with a single small and cheap ESP32 board for less than $20. +You can build this project battery powered using ESP32 deep sleep mode and reach long uptimes with a single 18650 Li-Ion cell. # Hardware From cc563fcbe23ee1b8e0decc298bccaeb275dddd2b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 25 Dec 2020 16:55:12 +0100 Subject: [PATCH 060/104] bump to v2.1.0 --- platformio_orig.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio_orig.ini b/platformio_orig.ini index 677bf5a8..3e6a6b9d 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -47,7 +47,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 2.0.6 +release_version = 2.1.0 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 From c9b2010b295b920f64cbd1aa9e1e933c293192ff Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 16:15:26 +0100 Subject: [PATCH 061/104] removed libalgosec.a (now external) --- lib/Bosch-BSEC/src/esp32/libalgobsec.a | Bin 189176 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 lib/Bosch-BSEC/src/esp32/libalgobsec.a diff --git a/lib/Bosch-BSEC/src/esp32/libalgobsec.a b/lib/Bosch-BSEC/src/esp32/libalgobsec.a deleted file mode 100644 index e9e4ae2331153f5a59560498f416e64519b189b1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 189176 zcmeFa4SZD9nLmDKCJBKVHH08Yq8$R(2#6sdL>Jp+2oPSZ;bjz2n->xw8c0lnqHBLm zQj~zS8>3x;(smNjQdU-E?Uq$(A!`*CyO!G3`qH9nTWf7aYgeV#`F+3VKIhKK%}r+7 zZa<&jXaD~P&fM>L&U2pgoR@p2d+EdLi~TmCyAOD*iZ#`5n!Z2AA~W6R%nx8;8_ z)AAo{u>3!I(enRix#fQu$CqEMX{lRP8R1WLQ&ZjgTB{$U;pS*lZFOtivJEvYtD5VZ zTI(Cv8xpJ5)~&jJ!Rlpo>suO|m)6~|p}x7!)S%LrRj+GmSg^kJ#)dhK>zb-t>uc&8 zT5mSAtZuGeSGTOLxxTug{+7Dhvg+1qBXvz(Yx&Jhb<|riNV)0 zKmf^7sJDSgU$EX842FFh)HxvxjO@DR)zz!&%IbLj)U977V|zdbyHW{@Y=g}HsZ34t z-1V*Xtv6Sy5nQ^ywhTi-P99sOq&r;5l-5c^)>J0T?MQg$UzIe!BjK69HferG!ZUwe z()^BuXa3bm^E(or`PU@P??`y&Uz;?)BjK69K52eO!ZZJMN%K1rp82m&n%|M|%-@hS zza!z9e_hi2j)Z6a^-1$P5}x@Rlje6MJo7gt&F@Hf=D#6nen-MH|J;pD%hokEwywpL zS;-l&v2OM1dCiUMBGv0_Z>q0tT`O~lA*YrHW=1mf?B(WHx*4|M{OXqS4eRP_F>ytz zTk0@l*DY_ZUUhw4bLHSkNwYfFucVT-jmui=nv#m~qBU@#F}Q`iiWygrAq06bN-R-^ z5VWrWgI-o@hG;K#sCOY1&2<&cjjQTfuuwA+LiT8N=>WTG~Q~w+h|Z(qp`eY`nQ@b!%Q1m^i1o)Gwa!3cwhzkrw?WR07)`r zx8-4kYu@sbNZRjSnOt+$GIJ|dX>U$V76wLbU9)Zxdr7UfZIw;Uby#gXn^eU#rLlKz zxOo}&CDqOIHmqO8E$V{x6%E)GSQ~CEvg&V~hENDxXlq~9SX;NIZavPQ$`)LfWN)Im zXjx~B8QVrPxh2t_Ci}*^<`!prtP|?j*UL^(bJXUsveDS;Hn&zbH*Q$3cL`dsMeXcq zOlxyvLx0;b+=jA}$sShP{aD#D;D!P#-Ov1l*#pfTR7?K`9mGVJX@fL3*+vDtn__)S zYx4$m$>Dty-9YP6kybqi3$uCEoK^L$twnaUz6G}%s-;C68fEtiFil2CQ)9!;xP@R# zx75pBmzv6!8=9pv6|ydL-$?9#TX8__qGJ2LVmMqi!o5oL>q^V2o=CK5P7I1?4wgP&4NRd=cMD8+4->VZr-wFV>%=x$F5)$^VyGDrucal8&- znnQPBV(U#CTI(AI@cR!nk7U=0=PBV`0gn^M$z8ASWRQbXzTeOmo;k3|8o{jqfHThY zeeCHo`rpT%R#-T5=FD(-+Qr4waR60~_~P8f^L#!ZZfD!%vp#PnVwROQ2mF9#%}~h& z)*P!up?7Z1R>#49W|msv-f;Uz@e`R==^x_j{GmX8?-_Yl<}RDHd|dlG@qHOvrxyjR zL+Rn^ZB^5|`bMRL3E7)Qr(K%<#2=8}{x;(w|I*!`J@5HYZhqdfS>1oQ{WKQX{`2_O zBILeg=1M5ZI5Y4``>XM`qORtK#IgoKG9`ZN;XIk&> zdHM5K?!7-0487g{pG=3?*ShnAc*hmiJ!8+$Kc3c|J*jQW>)VR+{a5em z=$shJ4o#ok>*-a=S=^plRUR&39z+#OHu_~n{$1+iCOztw-mvpIPyUOM-M z$+dMy9-EBqsKU)}um6QP2A89#a<9@>`o`FR+Sxt(oot-sK!x?LK`ftC;I&YjmW4bkOx@B$t-s5CV$H3_8&3t%JS>q8+KLSuq%6qtvDGU zdGgrElP6mbA8tLlcjVLm5sS4xy?5`PzSFH(?ETmF$E^`BRh)Aw%bHYptgG+XCw+NV zN$ivrdV0&q9loc=AFoaelm^=Oux|stK*cynF6tb)-FI~S@$qRlTK+Y=hUe};wL&AF zu&mIyhS!5n-E-*$Pygbfo$)hQesK0lj+|Q&8s0nmH-XcPTq@5Mtkc5br&bb>K)&Xz_w2?H|yhP}2zL+kZpc8F)6W<}l(w}aQl8Nn z*qR@S3@ZrSk$-+bkJLdb1NO{ntKzxV-quUb>HvhbQ>(1ZAyS^IRK0G$u!dcgwc_oIG0xJ%oKCk-Pei*q`y! zc!6)8WnD00QP1@cg{I^$3A8^&nS5Vb{v7L>=H9tML@+-H`zOMU?&*<|U;)>9Q54|7X=RbdB z`zaPX%IACN-FU?%r%HNGO)s_D|C`M5zl~Q+I#m%lRpOsC{x@;RO!rxg1{P+GpEUl> zctz2vLjS_?zlvAnp9=dHj{ilxA{&Ez#P~OCFbSta`x{hMG2)aJdn*=xU%D6NZx4hr zw`A<>?wD_N4EG%$ZiS}w9`R>A6z(YX9oHuGjr4UMc`EbCi$-y(ePzmuKz^G4=^y0J zx1PdCY4D$)arVhQft?5T{!?)8&bu)U2Y2LlVj9LI{Mav__&sJ)doKMXURc=5xfJ8Q zq~!hJj_{VQju+F4#*UspDp<6;WM}s!J3^z17Gy+LjK-`A7+GnK#KH}h)ilO#JIneE zu8bXhmWBCq;`M=$A!mNZ8{R};OaBU;)Vy}TxRZAUcH9IdE{e!e3* zr#ZT$CR*7My{tKUbxpLsBU;`Zy`m;s8;MSgM9+;xKOKpl7l}@bM5jce7j#Get~*-L z9X-E0I;lH)ad-4H-O;HX(FM)XD{G>49nrGp=+c^KRc~~CM|5s;bXiTbx;NU`5rxsq zYoay1(WZ{*{N`x1Cc3I4y0|&IvL?DV5}g@|&Wc2fBhkr`XkjEeyCb@=H`>}8-SE)f zc<;TL&CwM#(be723!9@?)kN1sqL+3=7cCC$?QQ>Oj-e&J@zC&!@0?oF6K{_7Ror)K z$v5LQu_F~^)`*n{=PdMtduq>g z#d_;tg*9n{V!gD-mU_B#6ylw+K)FA6$LjNU*W7#Zx|+~0ZvCM73M&|$y!wi0JhE&U zW}^1LkC*$FF3W6cZd;mTO)lF!Ieca3+Uq?Zgz}E> zF3DSYUF-a-FYHdYI)+(0Zw%hlJ1SLSt%Tr#H{V0LtY6DhjOJwrZch{aUu8dt77wcn@PU zv*YqB?)+mM>$cEo$15jI`zYQ~^H_Jyp^lpGb=Q2qqvmjT&5_>Sle%}$E2_-c+q<(n zl==FDXx!6ZUuhlZ!Y34*ycgH{?Z3;9tv+!1$^(~Qo!>YHg?;?tA3w3rivGX2E`>r* zW#ny~RXi;J!I#fZhwQ9}Gq6e`e7XatDy}R~Z{ii@*y_c!CJ@KsgjGaV=fh2?g3m!ZMt-}BzSVF!+6^~KWKe?nd5OPU`aJ8KMVd{N2o^B;IT zYhgaGZ9ims#5cCcpEK+3?1=BIBLC=FcMmV}pLuM0zJJWNn*0Y}QF-zL7iTCSP2e?8 z^lu&Sm|8oI_b-jTvhs%Gu{U0iop?4)T?t?M(Oa+mN!lhd{^Bb(of$eeay(6~pLop) z)>T!t?hE|)?IYSx#M@TK)@H;qtV6@vUuO+^%dT8~+Y3)XIXLQC*ITw?-n*E~5P#X0 zxN6>S<5pkn@k7JL9czC<@pfk)h&{fyH|x>1zS!9IACs5yM*PsQXMOGJde?hzIj*3- zzSthSp`LgJ!)1?B@QhM$@5uwPJ$rk{J=zz`YkyMZe;$P%h`sX2L+y_%k>$fefsT92 z`|dsYqkoAPEzjKA@pNDJSnGweG7scr-t(xxdwJ&M&L_A3=FSJ=ukDM!yC)vXtm*k+ zY0THWyX1Im^ybeO^$xdA74Lm8xVLYFg^fe-*mcvx4|Ij6clExMc3_lMG;Z{FM-4mN z*LKUXrHeBTtQ@_xY-Mieq8%@6@54~%C=ZP;ik-fcBRqGI#rz$mR#B<1{X?qi z@cWBmpA>c;4*827+|@SWE}Ref4{ledD^>UrupP6~5U{?mG7yF#WFibl;EPE?gb)Jv zN~a^7fslnT5`nw6Q3yE*qY>iqFUFGu2z+tSP`IJin&N93bI-6%fgf1cq;>g9@UP2f z&7U*pl5oMQ=Ejzm*2cz$3qChLTwHtcMXRc7&kxU-T0He4Wnvg6uv#lxc_+AX!TQyW zR&?3i%F@WP%NIwN&#hd%VENporHhwcS~#LKvH-sj2JsXiu^C9LJ}J*WSYg+3io~_N zK9&og%|#k(AFMJtYB@&JurnaBDSf~^vKeXrM1*1lcF#2kv?m{-7J4?*kv4ls#v;z9&J!Y>fmy_Ej|fp$^mLj(>L^8bWDB43lP z*9Blu5050u5VH-KcNzkR8}Tdzt)KPQ`t=rsd5JYY0!;EMm-?xTN46(zU_bE4vc@5h zyz-LgE3ngM0y7WRxhfCyt`HvaN(7RZ9+pQvJZgJ5md-?=&LV_!5U8gDLAL|9zD%c1 zmc?;InOX$ag?J-^?w75=tPlCS5p?@}8-yZB70=2LB{5Pj~V^LeP46 zjn#JMQ3;5)5%Wm-iNIPvr=>>W-wmwC;WsD%Vjf~n8O-~T!6#;0`4PTj@QKN%{J$A| zV$DBf@QFEPQU0jGCq7O1KQZ{kER*st7<@z(>G90I@D+XzbLaBfkZyBw&(wl2_m0Z z%X6zoBA<8+0`+_YfkZyBK6eg~2_m0Z^Pd2wz2rZM!0DfQUO~u0pv`Y1kSIeu9)bMR zm=Q$2Cfz2CYdj2?I(Z~{$%n!B71-?-B1V{k@b?JJ`z-{LS6&C%ITeVHqv~^%cHJWIq(bw+IcI&5>%Ep zJd0y3j>OZDm@9ZOurq#uIW{P>0r*0}yMc8b_X6v4^AIq{Mn1yx!1%B5e-ePa>Z|O8 z%{-PMP;@cErx5ZHu16qwY0qdzGdDi2zqYeJWV2>m~$)3T8ls; zpIFa(-vrirV!(Q?yAOC00`vX{f|lX=%ks$o7=dGvH~;}HlLo+Y$PbYTVqB9R&#GPp zcKr&GNYOF`_Jtpz1wrd<1=e#rmw_beB-VZEt_$aU>iK6mQhq;z=6?@Z>*sQEA_C>r zvJywi6Q7Mhz7ocPeBuc>kUs&KNAfx6hjCj1eqcS` zIG^kB`6FPiTPXiLFvkS(i@+phj}4uGVhl&2_<(-Hg#?0@o@w-`b9JJ%JYWI8FM z^iOTMd0lIDjWrcdQ|#kfjT-9l=4f@pRP`*2&r$daw$~fi6Y=c1bz-44)iVcQ+V)QI z(&Z)U{TjE2&Y|At_7L}avD-ro59B=F@E$^t&kF0jDgsR&?|ge`vS0r8Na+6-xQ76q z9j9ij797-A?-#`T7wLKrd``bt(}on4Vq5Drwpvs9fYib`Yq6#_*ELj6CHK!5c7x_G;uOWV*xR|%Ufn}T?vfnCDdR=cC-EW2{~obrj& zFUDZEIje7+HnC{dv}q1w!;RCyD1^kS#&zrJ*5ee}sBQvGt>;Yudo1A;!F-#DG&MIi zaa!+Ba*a{UG>dnv5)@FEUNg?ESb_rTa%RSJ)P;o?+Mgo$)zZdH=aw)*0d)y9<9fSl z^8(H-2^~KtAq*6-!mcoTIz&baP2o$i+bH&eh#V4zcJDI##`>cztOGKhX z#f$Mw-=^g%&dY9s0?zFKn-@?^A2XfH;{*lN<=c#3l!yQY)MY)v3aCrF8NWCYQSxT* zGC!9F2?~H?Qa~*s5}bfqHkffPWfBx{wkkF+&~I76^0*C4P(UqX%{aHN2@0sCv>E5t zAwdDPv@_#eekCZNwqj;n_m<5ISbvvD1PZ9_MuHWjr$8|Uu}F8Nnn@&7`c?X_0PpJ)AE;v5Ot*7;TtL%tmNE8u5in>88n z5aN|8{Q_$a;yl;iSMdw2Rfv}X^9D~gW`o-h*Tnih*9PaRk+`7n{Z0ElPI-;b_!}?vrdMKdGVssM>{qY6{|Ky~ zp49%l%~mHFz5ZHYUEMld_w5%5b;Y@4ODtpt*=`sT-ix!!%G&xHTUJk3@3kh>4YfN+ zNO`TvVCt$S`LnrI{a=(+mg}`t|MiQ~+5es1Wbf$?{kL5y{^gG6-_;ELS2sidOQIFnL{qTXBpD z-iITvwZ3BffOs>Owvk616h7t1XWlS^PnK;@2%kLSpzz-j{)ad+Fz+vLRBeI_C2uP6 zdYOYC7BNCFZ}yxfm@Cb51z(`~X#BCjBp-B=xWdn2r+|oq;7?RB$jq<-S8X}6v{O2P zIR<#slKd4oviXSFKeG+K8dqcr>9O=F#B*Rci_RBJ5v3n|>R%>&K0Hxv2tM^aEc`<_ zzO6B2&O)Hhr*RxFm=8uS7tFErHNm|&ep_%3_&*SQ5cs$u!xbcTR^YhMz?TVT-I+!i zo+fHs0dEB6+K_yXy)OvurE)Ep840DS|2c9KUN6h5HkJB`2gULc1R0j)wHZopBM1^f!|CWEiWmGB=FK4t#h zz-l}|=6B$+9eri^0dY)IKY7GK;j=r*e;dcef|ufWxnSyNANc0t2h@x}dGd&Z!UxOp z&BG6<8-X(95eJ2@`h^_@x*vfuI@4G3V$NvT4s^R zkVmX#3Mj)95%iA;)I%O|Q26!a!?4*nl62dC8kl?%v2NSxz~u8>zeI2aj;jp}XzA9>QQ1I)JFBA9Ky#%J?s_BO$6 zXU>U~XFGpgFpJ}ZA@ak(4+>@*9u&;BIwE)qa4Gh#lrIFX63lvVZ%uwN@M^(;s!er! zHXu$JwllGA&o2r8Rvb$ZwEXQ-E_uXS{#?<|hq1+i`7m~g;A?Tb(!h+fTt1||NiZM4 zenT)H%>J`rKCpdUFdxi5Etn5x%V=9Ven3AIK6%7?tuhAjoHOk{8YdVBdtCuO`)H-` z7i-K+pz#Pfz-(s*;+Z&ddeT^pjZ!c`mm`o*9uORM!H0DwiDSY?V?nB~&#*De z_E7<-1i>fQ({f_W?W76>-aj+{q=)z}uS>=CTS zhG1ob;N{4#bygX;!N400yve{_2Hs`hJqCW*z=sWd%)l=av(rFtAZUEjz#khp6Jtrs zmr$Y&Wi&%gx+o?+k;19NSm%UxmMS_3y3_*MgV8u)Gl#|->{fe#v( zYZYDYa|Y(K3C(}g!0#KFkKnXS$iQO_Jkh{~2A*x;asw|naFu}@47}06n+)7#;9UmZ zLoD;d!v;QV;9~}U(ZFvQ_@se9CYE_76O*RKIR*|Jc#46G4P0j63Iktb;I#&BHE^4O zw;6c5fp;5tzkwe!@KFOlZ{QOKe%rtw8aNH}macmivFz*fh)=iA25vI&t;AWj{!RnmZQz)JA29Gi13zxy=M4M`v0NkGH1PWd=G$>vCS>5T#BxoU zXy8Hv&o*$mftMS&%D@c<-e}-W2JSNOE(7l|@WTc^Y~W+Wa_xK3z;77%q=7#+a3-!5 z`dZ2RxEhBIJjKAp1}-yjg@Laz@LB`68o15C+YG$jz`G5+-@uO<_^5%OH}DAqzfCM_ zfDa9vhU=B)XBjxpzy$`LVc-%2FE;QB1J@e3$-uW7xYNLQ8#rd*2Mm1Bz>gdFIRn39 z;5QBYzJd9QgsywYz+(+O(ZGcUo^9ZA;&ry+( z)>rS+x9tM^xfu0vpRM)X*s?_?rUHWdFn&{uKp|4N$!E{A?uvQ8S=0WQ_ z>#YXFHBpW<-KB3aZptqQpZd;2(E64EnD%i^^%cbFo^!2#(eT-}5^;96la!I>f z`nKaihCYul#2kH(yY#Ic0lR6ikk1i?L@3Bd&?%lEz>h5tlFm3Kb{DDWBmmjrJ{q9L>?=r2+ab zMO>5CN5?Rxz6R*i?FUmGeb-&OBj<1-OXdxW6+*e$3C6JS)tdiDp z-xN98L|Os42{u%}tRn-@Yx6w(d&r?G4pD~mi0HFpmh~@?Ytj)MwcK|gXST~ODBFYd zQiRzEOk+Dg>C!g=d&k2VtD3aFK9|0-i)}gT<9Vm`jReLNu#Xo{(?b|U_OkBLHBpS(EexNb}Cx2>o+tSGtPwl0> zYv8;7xPTZ0KORPm`t*RY*?iZY66B(Xt!$LfE~J%QpJlUrX|bvDdUF2;l-DZzGu0nV z^8GdH;q4t_QW$}lwVEK9!;sf7^3TPQ^JR`)AUO9D^T;`fn8R(ffdQT80MzT#7Z-!C zP6e@ny7Y5{i3a9euKAoxH7+L>oxHYaTxH+}18+3&CIbVeMEcay)zGJ=&dfF?%|hU4 zNG8X}kweMp5p}i5=@IKLSIbpHj_sUGj_V?Kxu#BCkJXSPE9-%=m#*7|k>QyixD(Y zhV*>|jTwIpA&Wprr#*r=Z731`o3KxhZ`M=WcMMqPq3F8^T?9fp?McKLul+_b!t3}S`fMA>NyRf7_S3I<{M}gIV9*{;T$P;|KC%m(Z!G)j455c|YzrtLea5*U``B z`};dz@3^YziVy!78f%Rzv5I?Q_lA3G?!O?@UJocEhWLR^bd99^8TIxQ#(LXqSAxBL zQ((#oxSHx)Yx}wDjU9z!xAzpUr|Lj#1Z9;U*uLI^13eqxeIpL{U3dN>{&e3X2SQsv z3T=UFzu?|Q$L@IaiV^YGd&`ga9PI1e1ou>Tz7jt)qPHe?>4>*pf9d$seWABLI(^G& zJI*dzY~?<>=y!L#ntSY9U;9lc*wOp^ftvdc)ZD+dBUB&CyY*Dx3hTg%jH07QdzZm) z;N;FLp8BM_cMn`Xb>8~>qN}W6=j0t%tcZ8_9t7*nTTk^mtfAWiZ2)J3p*^#TH=^=w z(__VfSisunKa}=tdi(EL;R{AU16o3HRGn>3Pa*teQA+vRBoFPT7;oxqs@W9l4b8H8 zd#WVg3C+uYaF;SNhQ6AEz&VB2qZk6`O&)c)8TiWxa0W&CBA`v2RCq$Dvj!jxSYrF9 zRcu5x>9{_UZ(=Q{k7k;Qb-F&zEcJ!bA~pAyz8nt)_W46sN2ejC&sL~9sI<2Ir)+~WM}(&RFdp#VSuyHVmNogtEdLWztw?_D()_xj zBNZ5#aN(K$`jNW#j?`Z4!#I4FnWpu{JA8FDfnay6D|n-o`BK;BHrvNvc=i6CN8*vy z4NF(A>#jb#y^rFyPs^I#wp)&0_v(TC`#J)l-pkUu$DiHvP`uAq8{8OsG;D=l+IbH~ zU3f<<+_^unz~8ec9$DM4bnUwK14_rZQ&!Jg@Km~{BmchMd1=+V<5g?JH8uBPqgc8t z4mXC=z8;^4iE7%{;-%Z;OSZ#j=Yd$DeK#{du(SHBs*Y!>I$F6kM^@DP4&;BiFMr$d z{5$sfUq4dy-jQqCcTyOQm)pLb=*&r()tk+HEdl>OgvOwm@C0kkzt{T${;rxMS+D!* z-t*NS7(cde{Fvk8&#WI`b7Xn_G>qB+9F}dSqUONJfNykgP5Aa$pu9VL)Bc_t;*m8C zOV_MxZ)3XMK-blD^tQFFZfmwH*buK;!wRw<{`Q-cLF2|&uQCimXI*X894W5%)f_22 zK0fEb_|f$vYmOAWex&@pBPD%Dvg*fkKB;Hknt;`e-n#vk?EzmlS{Y4@&gvaM%I>TV zbXFDRI>w(3bI@7c0Utb(ZtX*dU3aW^aawRoXdF5!;LB>-)5f0K8wh9g(`o@o&U5%{vw@!9P;O2 z$n-0JEArp6G2k1)@^6X-=F|Q?4<)is(Akw6*0zqes_bbrrpKB=i|Xs%n=o<D zKYjwiGjGW#ef1Nb#5wi=Ux5-au=~d6sVOg#U$ZoSRr|Y?Z4R7{{x!$q4?nTTVxV+v z`>!by>1~6^b;xty!qw=4(q})x8E2Q;5lD~ptO#ygI6qw070F4fe)5wwIgvbk^Iju&98*sz=Cef4?R6v@AsA75n68yQ^dlX7N;3%hYe6mbH~u!J0kg2OY^G} z=H8*z?)5ge&C%N&$b{aaC*vh^9CN967WIDFo=0D!(thdeo?W{*s5o6+Z}P{U~fZ14Vl7bf}wk@Py-Z z`M{BkzL=kGmzBRux?L_)ZkNlH+hu)mevt!KU5K4)^Yac%uFcBVrIO*2MqQiL+5W_T5Gu8mWnzoyNy{+G6yy$-{zuHe0Ux!esN z41X;a7|s)|yI@^MK|`R-+LHdrfsWp`9iz8C+7V2<_17KqEL@1KaV1^fZjbHB>aDr& z?32&~9{lPDO4`B?g~M?7IiO3pEpU-IR4PbaX4KMo&MgvV}A8>e@20Y zRqtEH^glWsu1AZ0>_0y0a~J#8eBD29ycG(3Auy|Qe9znQj;~sm?3}gY%#Pt!ch7ur z`k&FXy``%2aNEsy3rnHBKg{c#b@lk}khSn$YsZ9HSDo2C0_(ub_o_X> zS!qq*ZmHUFxa$^V*?iYe9vIVfXWzZ~JXidudZs+ASMzx*&}w#`~SV zq7~QW{%&&g)8Fvl{$u)-XPtYW$3pqK&^e*g`W}qGzQQ`bBICf(qkUIf!M#{3KmAE> zFMPLRt^CIWE4fzQ`~Ri2G6z6neBHIPvlo|vj8nnA;jQ}*6nuXE*U}FZR9=~zr&beN zm#(dhto=Nu(a>n?tP<<1JzaN)yVZPX?&k&!DyNL#mvB!uv;!X7H(#8Cfh3O1J9_4C z-2#Wu2gYV_w=oCq`}G_NU(f0r8t6*yxZdKNh1~=vv39kWP+JRodL$o{l*YwS1Me%iiRS=G+U8U|_~Vy&?sfNExwG@g2XR;n_v^3yJ|0<-)_w1o zj+k}$*{;o3<@WBf-a5E><*42uMpt+*eDnGbjQiYoANEI9elE9j_4b$Vd@J7b>-cML z#JgUNg);NDY+jjr@U*v%ZoWF_K*&1qOg#K(*MXP)eYwk@M4qdc=XTz+{bkf8h4u}8 z2`&5Is%8H$NXtgrPns?JJ84ZctTeLSu)3A8Hq~vc8upjA{8qHfH3+Q;OA#1fBS&H# z>Z=C?wwaA!&Kd4=ZjWUB}7R_vKlpufX0w@W z-v+GpaPzPEF<=tqV+iR8`3UzRkjU4h+m>;SrvUp3EQ`mr#0VTKA0yDtE;2yWL#*3y z7ci5^=gU2%II`TI0e1;r4+YFa{wKIO)+zkk(YQAX{ur2jN|`8(zfv$?{^VpvK2Iu* zuK^w={8hjt+Mr3-%k0N65@CiKKg2LgE#h!cdCPD-8-e_41U_40xf>Bq6MP$vB$h=y z0f91m5J=<`>wf$;5`MCWw4uEziXxiF{%$e-fCslmD*4=Yo?&zFL6dNPGBV zKZ$%zx=k3@`2TMo-8zXj8LWNEk-)LBSh9Gvt?n-#NnSE)J}I#0l^n=W=M;o<5!iQ2 z49xR`H_gf4Wbk>@U(5dxI30m9KSl@%{yB~$FB|lD<5AC(oJVOl^^_oJJww&k96zdF z$oIc_o;2$_5q0$=pexn9!FHP~JmUEXB(L^o+xn1^M~(rWu3-e~qRw&zUQ?)ta~_F$ zh({n$rUrre$mhb34^GMFqK`yAG3PGwxiBP=Ppsu{Clf?IvF6_i%zXI>cOj4{qe;&p zjB9L;ZH_1AXPbNqfqH5YMhoV3P4l+{>-qefz$CA-25UF!W?2yg-ELg0k*J4Qx7#&j zg2*S<^<6_Ih4SWWi;uwVth#aXTGn1Ct8*D zE%JD4DZa{jv$qS}!~?L(1avuuO(Zy9E3FgW;F7g)0@=^muAv1EnIzWV$x#X!#8KrC znr-)BgBM=kVoO|6-B9m#`)k;U&tO+KtlH23Ke=U%H{t6R>zeSz^)-X*9pJre(AvOv zloXVtHV|B}zG(wIXbx82q%O>o@Xw`!_^84>)y(s=v3eXZ;dq+TeC3AOAxclVWa9>+HM(&sCSLf9Babz}i1IzwH{h z{>JGzGc+!8@CJOb`>%EmEISuY-qWp!f_!3wA7R1@!Zy;pjeC~CnKj(_S?TY{26`z=o%fshg5oNRm(=n0>zXS)c((9@>Ko>^ zwAQbyZiQMiGQfaHbxR$#San*ZVS_8lG^4z(x|N@?;0Fja@~D^}(kD$&lKXuE;i4Astt!TlO@m3(ugE@_ork~LnSJSUm-Kn0<;T9UI z?uIOVce@7^wnd8N(f^gTjmui=n)>-z9;}H()ZL7(e%Bh6m^qgEC3siaEMds{nu11` zCw5-_?^#Igo9l5q;g;&w`o{H@$y{j9UEhlHt$cQIib-C^Q7U23>y%cV!1;c1;wO{j(>loT09NrDQX$s$-<| z-$rl1RPL4-Z2vq^&b4WF3%EDGD%}`X{bnfYQeT8pQZ?rE?;d{rMZJ2UzeK+t=%3_0 z%=*g=*#rGWjdn(YedEmCDoJOOn&88Ixq`*RDxqhit^b^YS|l z^;mLvH{yWX5lZaOyN942dHCcHWw`m-rgss{;wK*KAzBgmgl<&KOpX|$tRCk`-kVc zQ2T`E4w*9K5o@3DIh2`&9}stalp&8;`+?^=(T61=sT_ea{S!N0|En9r8SO%+Ajm(+Qb;j<9X zLI$RiGx;0MIE_LVAV`#XgUdH;ye&*vfdBvnVjsxE?+&4QI}f|U(| zRat^np9&5`ul9k)_SCq*z)F7v4A3rw96Y;X`Tt>HANvb@$|~RKBC|mFw5v`q^KKGM z8^0`=ek}Pa2lbHd63lx4qhRXaA(-;Y=dhF;6F&3aE0}rzMKH_#je#TnL|y6xvz_Qe zPuG#(x*=wp(NBctUuEDg2o6K$ZiD|r19RWZyb~c4!u&zZHl8Gywsjczrv{#eJ*O^r z3gmo9BRzpYK6%9S+sSr5CVaN#%Yt9S@n?eBW`)4o|M?$H6_7c!SN1*znpq#0O&#ClSiz5ysto9+rw{1u}#P$*7jUu$mGfxCXZOl zR2ec?iwt?hTBg>J`I^X(N33Pm8Zs}540*&_rhzijKK?Y@X7Y%2`-~JmU)>sK;3no> zfFICjg-;%__G#aWxNeggks*&*+pv)`V#6IGLmsiV;ZEW6m9dzCZ)ILyC_vv8K6%7J z;kO~KZFos!$RpM^oPqM$UwjR1w1GDnd8Z4XJYt=9jqv%}+BySw8hP&!K6%7C@Arhy z*WBpakTwu+GxEMJeDa8O-ZLRbdA=5xXW%X)?sDNFzFD?`&82f zGhMA`AfsP5^DPDw#6NvyUNoiclU(>Kg?}54)q>flnF!iHzq&>vFL~-3eHj3VYZ2;X zz7T@%bNB&uBalxXaZvbKz&;uRdJus!h|bwa$qKE-)~l;}27f0Tu#MV*uD01HjH00QMXM!4R>05&p`zoU(1K%Kem$l|AR!SlJo2F@Js6*f5UVD<@@1%9b{*5@&ER8n`X8I<< z-^TGD4E#@mnf}j$sq-q}m5K2F!NxRpAFzjG(_UK^&~Y4@mV3PY{r52jnG>I4FG8k2RFA_e#`5-scg5 z!e>AER^bQKh(HzbI!8ybR3A8&T&c1e1(F`aa4T-{++<8j%xuxD-kG99&u3ks_yk*fL0?=hCJe+ z@Ku{!2L`AGfimO~2ZgWN>UuCh{|A9GLJZ=%W_L5@8p^gS7+|1_H)#zC*UlpY!dDn#(fwY8X-27*cu^n6uL zOl6=71bt4eAeOOLMJ#mzbZk@YkWP+$ZtMM&s+aJU4T5JwPRporh0pQelR8!zn0>AJ z8x739)BG+2?=tWn13zrw!v;P^9K;XkMFfrCFz`tOe{A4PUqU{|z+nSVF>tYg%M4s$ zVEVPxHmo&ptAX1Lyv@Md4ZPdH`-$06pvQ=1%p5iF^9DX);I|F@p@Gx<2|ZZ`&NFa< zfz|gcq}&pNufAs?eDysG!L^1=lYx0JPTSvUVD&u<*@MK$7p%T#Ay|FSLh$2;%yR~Q z#lZ9ls`aSvSx8>IDCO|g_bh}z*5FSxu=<{b$f)mG2&T^{tzUi5Likk%UwzL)_!|xW zCIhSQS%{4Ko`v8&lsUuhclAAsEE^vtU$FX~h2R$r{u>5X-?I=I^*sy0y#J~7sP9<_ zKWy;T_bi03{N)NRGh`|Ze2szE8d!bLLh`m5eDysG;nOd%wr972_Z#>z10N+GX}5{` zo`uYlC&(A9zGor$LxZ1&dCND-&YNXm?CziD?9n)#7zGor)u?Byl zfz|gcL}s?ZFE{XV16LVXea}Mj(!a7UcawqD_bfzam%-m-;D-%-*uVq6XEDZZt2Zbk z*F^O_3&9^7eDysG;j8ai2o4WRJo`v8x zBd_|Nh49t)ECla1WYqU8gs;A5Ay|FSLa_Rth2RrLUiCc-;j8ai=>140?3cBH`ksYg z^*sy01%{0Jo`vvB48HoFh49t)ECj3XS;%@rea}L$`ksa0PU;b?zGopgX7JVbEQGJV zXCYX9&qA>Jo`qobJqy8a8hX_CEQC+zf4W@tJqzKh?^y^|-?I>`zGorpYV|z}ZU{i? zdlqu9Lw(O8tk^04u&HoK=IN+th8*<))0Luy_)SG_eTs71QRBONw%Qu899h?RzCfxldA*E9N>Sg>u)XD7QO_9Lpl{+or@=DkyS~KP|T( zayXTe%01#yZkw%I{p#`j9mo-vNLtx6d;c~Mab9|v#^vsBT>3a3wHpRNCCB40UHXnf zAJ^p6$4jBsHiGWAVD(E8qW>3bXcyv`%8y_pAU6jcCx3`BiX5VSt` zcN*@-9+K&rC`WRCr(tnvqTi+>UF%zr-oL(wp>Mv@VE<@+WsoB-k+kQauMn8_@u>Cj z*{s=qnb@D*C3$$z`qsMi@n#ywGxZgT9Gc4dw&?Q%lXgL$SABQ8^kvRT)R%L$w(nOi z`;J22HBo7Z-AN`q|`kKlT^({8^ zT`Br>ecy*Zulg=^>DxFrQD00+j(y!OePQe$F?Bek?fZsHZVKdb9H~S-_PXRs2GRGg zBFC{oS_nDL7n(Rm7`_iVEWsUG2RXKfCd!b0B>LD54zoVMsXoxU~-G%s8&Y-r-lst4ZtQQTHv= zmXF7MPBZ||IXzBMH7>jR>8r&g^>bX4G--YI>+jBIGs2F~?Tf`Uv5S#9}kPj|rC_S5a%BYl%Qw@7uZneC`={*uheC_clk zvbE25;7C?zik+c6y~AfMoYPTem8UP99`X4ib9}|=lc!(oDsx|2zcNuEinUb~1SUK6 zRqczyW=zLheK2rx&mmuEO2n|RAU)#qPnr;kSOw{mrsw+t`Ez^)>Ck`z#X#N8q0rX+ zeto+5BR=lP|J60NLi*%wQ*r9IlV{O#iOMFZ-YYqu?|&SM2nabTcP zeZJiOW#yM<jNh>nPDo)Q$l%U4WWr0x0Jyf@yyo1$JgS9iQVEi4O#nZMV zJXx}C8RwT?yaZ$DI~lprt1>Vg?M~e0)Gh-f_hDn?cD`MnaYxw#iq0%W4MJya&F}SF zJ3B-E*!g#<@j-riINX}M{T^p*Z2eMxIXWh{-(2Qm8d`?B=#pV{L(N;}e@&OZ>_vyvzK zN_DbFzx&mb&!%TR6t>T!&cMnQSDEKKM#YS?BiUAdwr`(r(L5^S>MxEE=pWo*AMo@{G3lA3Y~L=eo!G^+T3fHu^68FCD9%Wi2+!LerS-jPcoT zUfnk6yy`u#r8vi^vDJfmbsWuc3OwY`Tikmj$37qO7H9Q@4EEB+c!Tv zOi!D=u|0?U&t_!pl@B}MEO|C#pKsiw_LmJpna^f;+OoA{i=ORy&gyf0(GcT1M|XVU zJkN95Bkve5tGe^^j(HZE-D!rz3ANBIZ&HtZ@|4-|8>q{2b_278b?x)D( zT8&3O?&I2zYenwu^AQRWNLn|sS6;3OnOCpLDNl^;i_$|~yr042r3j>CI{#dIsE>8w zQQO14JBd7EEPIs=+{0;mE(0bdvu847SjP(~3`!=?`vH{aeI{)eZ8#r+?NEw9N~Wg( zGSs8@S;^#a>p;mza3oQln9F^ZTaG|VriW{Mmdk$N-iUcq9S?j=$Gq|=NR>s*exaUE zBk1-|wH+oR4|NtGkdoOySo`a-J;R1f!KESI*Qi$MD?LLeoxf#(48vfuR>;C(_q z3Z@>`MYjX@Ya}n797EK@Bgsof*Oy0K7RzV!Y`GcRt#D)P!po-U7b z2km4XHIL`qISABKjF2aIIgTVRou)mUL#UHuiaHo(Adr&fJr6R>%X3%T!}Ca=3!Dc? z)bp3j3FaIXh7Q^`R9l(#`ky*a{~8KYxr>b0hz_l>A$5(8=+GM1 zrLNHt9a`i1)HOPyLu+hIU85s9w8o~?H9De0YrG+KjgII5jdXZvO|7kNt+uAtw6qLx zvO3g+>GFR$*o5g4b7of0DRVI40fxH-o!K=Nt&VUqtIe_en((XYOtz^gbc6>ebalTo;ijV15l*JnnRHXp>If&(>P)<; zXmx~>X>}&wRJ1z6$+S8Xa4K3I;bdB!NjMd)j&L%q&P1GwR!2CQR%bF!MXMv6Osg{? zr=ryn9-#HF{X#wWTABv>_vNl4K>_ubkY=2#`ve8l9V9bekca^NzwiH+yZ;0Y^!FM` zBmxE0)BFT0p#G}NjPnV7f&$ipL*0owZ4<9 z)AgN59p@d+1P%0u&AWgJ3aC2=W}G|s1P%0;%{zz*8t6YeoJa%;ICnd3c0k=}NQeZ~ zUwNAG>k<*5fOWkqzD~WD#QS%=r<9HPhwss8V(at1AJZ>J$i|&ro?!|3N$Tws-jA58 z(x<3zcr%^%CbO-n%1;5~d_F>dwa?^;iVRkl;TAvQOrH_$;gHsWw^2qpa`+*fav>TE^4 z76SbxJo7u7jzRM~5}x^;?Zu$^9SP6;&PHO;{EmcYerL-tXnse+GrzM#7&N~l;hEpr z3k;gyk?_p#tojGd??`y&ch>iV=657K^E)f~LGwEjp81_M`=I$93D5k_>U_}rj)Z4^ zXB|Fhen-MHzq8^VG`}O^ncrDk51QYRaOcN7b2b8fTJkuq|016{R9V9YDGPj(Sr(sX=3sKws{maVuiE63h;><6uAs!e3cXtoJgVL2FJlH}BPg79%r&wsRj)$iH%^IoQD`@_IF;FH1#TAt6* z$S=T=T|)mn9G_~u;bD&F*p>%A=6Iuh^ev-TmAYKYkWVt@&GA-gIQ3$?=dgi6#5ynQL%%UaQ|ODP8fV$meP-hcAonAFb`zvSK|kR0_G{w`qlW6CA&F(lz#kp>SrRz)UWJE zV|&g~>;%w&ISPF0AZdG4f6)*Sv97NgC*eWHC-|f=f|fVO$yD%jkVqb}mQmvbe2x>6 zwn3ePTpWYUb5Pj;gQfc%`7q>iq$0#x{{R~x<7orLR?vv{j$F7fwpfG|@t{psQwcnCLVh%dkL}3+w6Dk zBZBqS{v1{ivp`T4f=@bPEwM}>O~g{cjl?oXwGqpF(n(BXL0t&?Z_>9D%efpQmh*HE zv7CeZiRJt{NG#{hVPZKijuMCQ19}d@Cu8+RVs;4V1cFb-%$vmMQ_@KUpEUS~#L_QJ z(>`R_WEzh(FjZ)Np@C-;OMjIcc)5YA4BTMgjRxLi;4Wh6_gw~7Z6*AN4gO&RA0y7h z4@k9%;5QK0eAN!Z|JdO3Z^N`qj)B9(GTxN`8NvM9KFwGDXN0f(&&XOu`JWNImhysI z4cum6<$p$Gwv#X8d4T^J;Vb_$f{#*O#{BaJK4DCu<}16Soxn3Jiz~qtc#TY8Ntf`j9}$|MzHcflV#hW{Lcth{$~U$|1*Md zqsTe;`M1V;9#H;gWFAodX9O$%GlG@>8Ntf`j9}$|M&^ww>J;2yVC8>C_{#r`VC8>C zu<}16SoxnBW!FpjpAmeRIt3pimJ6}+KO*0`g&nt<$p%_%Kwa9M;@n)VC8>Cu<}16Soxn3{66L7 zy2BSKHCFy-gs=S12v+`Q1S|hDf|dUnxn?Qr{F3BEB`aXSN><@8mIiv2v+`Q z1S|hDf|dUn!4FfX;KK%1{%7P``6Bs(mH!#R%KwaD<$oruhKZ&8&j?ojX9S0F&G89V z{$~U$|1*M>{~5u`|IBzxk@7zySoxn3to+XiZliv=b}Rogf|dUn!OH)PVC8>Cu<}16 zSoxn3to+YRux&U&{eqSM8Ntf`jNmj}54DW)KQqzJtNhOhR{m!M&!D{E5(6v$Gs0K? zX9O$%Gw0emmH!#R%KwaD<$p%--P9>KM*L|zukt@5Soxn3to+XiR{m!MEB`ZsmH!#R zwzlNIJY9SU86rBvl_gE$WjLlHSD&KX-6_gFnxfn@6WjynH2&u@GrYP5tqTF36%6%_IxnHCxr`9+r^zW?n)Y_#cMY%0W1mg~TJSDK z(4_VKz@@JQ`es9j`U()VzIR>vb|cNpzPDZaZZ+(iWav8w6SFy)56+u|*d)lS%ap)`8%0!OQAGq{wGVJ4+)%t$# z(ig%!O#3xy`+n!r_pqUFCWN&Y5Ta&HT8ujC;Imi2y1;ey7cXVzFAtC$T7C$%s z`YxgZAwD6rZ#VSm_A4>;opkBD2KrUzv`>Dvx{(R z+z(K92?FT>=qtyO_ANqS8r$}J6!r6HuOayto1$P()Sqjd0khZaOryk`t*5urJ?VAm%fjo4_)Vw zw(lL6zA!FU6CuU%autHMZ~Tb<{ab*J@j5^9Ajdo<2&6LTtHP1`u13)M7P|D^3Vj7g z*F-teT$jGxhJDu}UF*BarEm8j_BFfo9X0H$H1x$>`aXm{um1g-OJ4yN2b5!ds||h6 zy7U!b-eh}d()B&+(zgQosvtyts}OX3|J$W+6ZCnVAMd*K9W?B#GxSXe_V3?=&{v9d zP1?S5AV*vx32#E59)D}V)Ar35eY(v)g1!lohX-B@7%Cu#B!_t4h1X|Ilp)oKK0Ai7 z2RWrc7+UUHmwgjqKpPU-e%B*t`x@Q)a2~u*okCjQbuN9o(a4mezI6y%-$>wq0Nw7$6;o0hF>Y;0XyzkUrp zl_m92HfvT99G8KT`zYgHjz-wmtpeC}8W?S#eff;1KCA73f0fdY;}ZhbsDN*Jz+aJe z%F3T)jhti!COy+Lp)@e6G%Zk?-u}MrkL;P!t#=28TU&eZvdp4RE9;b%wJ@;28nw_` zz326;o^bnbC|CNccqp?X=hW7_CJeWb<)N*+v;32{c3ZwvbAs0N5!Ry48?(|2({MX@ z1ahz5^Ak9Q`rEo)VV7vqe;TYtV1d02Qy2o2!;GT3n9DBWe1rxB^q|6BI8vVc?Fb~s zt(5=N*XVP6oE=|TuUox(UUTERNcH;Ko9b&@*IHAP{QKSb|XR*UN5wfA*oOj4CO^7R5tKZ3{-;7P( zkm}{smiqPftF$dQG`HIRR&!7#*0L6Xm_)1fvrO)L$S03D$b}nd1p@gTU}WiM{GS&- zdBj1UuAt2bl;^;^OE3o{Nk7{*vC=8_j5TC9fVF&~fjR$cez}3UKG1x?lql8v&WoXq zb099uitIT`A~@*zaj9TEx(1VDo3fpg$<>HHv)t|AY0{%suT4rM;Z`W#1bjLIo0f8H zXZ8tgWqXp2Axu!n{$U!!4g_!nGj-46zBDA5Us&>q^y zYmc_?Ah6B@sNU1BA`sGP|AM%wFN8=Hc+|(spw{QUPW}TF96`D!t&c}@hNwW~1bAm4 zXnk40?&~CWP^x-$@b+WivawNY_R|Or!uPnS2gpD9?N(CI6RP*CH)-c;VleTgh;FvcTU*y95R>{=5s}AceaA zf;OdM|F+jo3^s$3Un})&WoH_AZPaIpPB+gy4((EgU6-RK| zq6GwIKzT-FU<70o6|^ccSQ+p8`|i8e&CbnV9v|Q5c|Y%JSN3nOv-aBi?6c24`|Pvs z-g=3Mh-YU^nb31|OES9aX$V|=8p3Gx((kf3^3fbAzI26OnhbhL!{c-)7lBgxbCrGw z%&p4ZiLW?>8wVIU-@B|5hhFV8OTY?*$jHl402 z9ro<7X}`&3N|KgYhcZp6#?kXi%aU0o$+TBi{g{BGmzhdO@DEAF%(<)){wSzj{0W#pl%RHinS zUYFG0Q!}dihq+8sx~1Vzea)1{u0tJ9qn}BoTc#XJ(o>X5%JmxKWF9@BMzJJ0bkF3` zjh%;-O~=2Y+VL;^t$rle^3FpoHHYfc6KhkHSjn=<6cuiIDpjA1tdxn=zuz!c1~Rjg zFc{g=l)U%x#~xF?G^$=+n4Rq1btd)FIPJ=_HAdO@`&`8_k9_Zu-w6CLy?^PbeQ)HR z{B3Uk($V|B^z{Db#(l5nZhTGAstYgt;r{*2O;7$Zx4&~)#mI^i_BYSE`pr+KfB0h8 z!sEM|o3||;*R^oUwx#2{79QTU@QD2j(>oe=EWD>VyJOpq<{eA#X|CSU)ph>3uJgz5 zsJmxwODLOPpMgTnMeIsHUh+WZ`X}eV@ZYKYq{H0mb7z!o+}~Jn?CSke>x0?d6}|y0v{z=7H4fTZT98=-fV{b42p^@Q$BSgUhe|_XFG+9WN2%>{_cjR``)%@R>!9wl7Uv> zYx-BtpEdFawR*%f>bjl%9$h=N?}2HO4#WM?Df^PMIy8&Z^q;xU4%Zw^WiqnqgZ8=`GU;^n)43%}+FM3TcS@#dM7~aHQcF7igA;EVduU%dv;Dqo%06pLTbq>4?UihdTZZm1&RXGN~hKQ)Rm8S|%RS>`{HqNF8mw z=TN>$Py0?z@|M~|+0>M259Tyc)KaCpQ9aZAb(JO4{yBGAs^b8b8pfv=?wopF)r3<| z9(ZWb7dgAbks@insRjnj&!k*!e?yop(T{5hyIa5`V z)TW1p(u~pZ>8TO*H50R{RgMnRQJGhs9jh^4l^!OmN$P}km1FKJ*}s3xtC>B+zt@@S zSjkZxI%@j-n#t2Em8|ro+$V=OXi@%jsXj{7?YsWGB@LAqbX+$4#NlLFnwefYtWs5{ zS-g}ga9G9IO)6!@u*&9%^`j;xB`QEVX*`rrk~GtVnG?=QjaXM%Ti-CT@v9W6s_Afb zrn6KTe?jiEQioNg4p$jkJ`k$p4s_1Z(K9)!da4{X@|@IRN=vJog|K6CXjFBFD%10o zP#LvMI5e(mL`QXQdg+Lkx-kWlX5M`A8MH?n^QV! za`kG_GY{2Q&(p(VdTC}>YDDGqiP_on8Z&B~!>F#LylZ&Nf|0qd=Io}!lN&x?F>GOT zc0pNgjs~a;)7h@h+?}5;T`(*+M{V_}_qVjP>#$PX`PH8<$o;W2od`F!98&5FZa%+b z{_>-0&bv81=g!=wyK-v3Wts_77HQRM)htDaU4NqlQ&y+gO%ef=FBe<0fR?aBvI zk8AB~BF7v&cj=3pu2;50qR=0=JkkC@X8*pjinR-y%JS=WZ%dW!PK`UQd?Q`)f>(3u zotJi|R_{N5W9zW5ZA#CnNm{9G<~i9RzFB;m_#QFO$p^#_ik}ff8lQChF;_97V#?Kj*tYQSZ3~aMXQ3urwk*_b zvt`?s<}FLRnya^TZCf~Q+rsf%YPy;kR^byaUab!OSd&SlqCz3o6* zvhmKc>#D|fyqvr4Q{y$L*pfOhCCP_%3@a3$OnBo9xeXuh+L79D?Q0vhcW&L$`TUMi z`Ck9@iMRZdb_Gan*znrMh2!_6^5cqKsg7`M?*2h8Ke%XmJh!_>5AD0w-1vB|e#?HT z*YDccxq}DCZ6oS;Hr3ac)W37XsmGhGr0khJDLp?szRMNR^G_dA^epdqn5Z7lN$1Z) z1$8_?!BngRKgxxc-K#nV6%>!j4w8k&WRqyc6_YFOeRTDLpXatT%~2u3y;gp+4ashq zF}$UFNESxT%fImNbE^*=qfGVm$&WKif0N6PGc=Yg{Z+2xS?p_2srH($q^^bO(w}J@ zmhA}le(6ubSnYqJR{G;y$5VOzai!1aIzn6SS~$M+xm?E+$amMwc_x=1PtUDZjpT=L z$yF)qIe+E#_y)2Tc|03p`PVdH{?({E zMCNmV^rWdeO^qnzm5wHYonysm@mXT1Obq^nxJJyWql^40@oX{j2@=vnfR8~zgpQNv z%Q=uQ;m5?}MfVByrC%aB`h;1qm~x&dChY<-L|X8XV#>e}96}D}br|`2G3k)g_3+wF ze3Ka4$Qib~oZ&l!9PIMX3A=u$u%7h6&Oa!G&W2Szf#!G@iJkiZb`dQ451HpIbAcyk<*8`oS_(m986uIw_gk)2fN&dY{n4$BD_8AsK`m?Byx$)7%b2Hwb;O%O6B2 zLJs!&|ADZ#!)JuO{$COH`hQaxA}zRDO#Kh^tkK~<>)+etOcX@ebn@$!l^|7@zebdk zdL$k9K6TP6hDZxWhw`iyL&(8|$a&U5#KA7V7NLkZxK51z9`V_Rf3J}GLC(1!Yq*+1 ziAW3P9#4x;jO1Wsq&+E;D;mhpH*TNxI)W#Iw6?Ff;w_w@Ql8V^+Wr>GCg-ZRm}2cJtq|UJ*5$ei%UdsM zo87jm?eb-9mtS;M(FrbH+<$fB(`DHX=EgOEk-nNj(UGe8?I zGBU(g-0LKVnC?$?qEPQ@eQoGxd#~Hf@V}b|_iH#TykJaH5+1tgFhq~jAJH8V>b;I_ zRnc*VT-}uE@yi*~^BC=>-s{dD(|u5=_qwvxMaOH4;tUnKskcuhkLYfC>pq$c?Yb$` z<7+wPjUR!;d9~@LOjyY&j@NZZgfhwTMe!2~FZ>)}u1aYf+qaY&OWwu_cseZ1UrxYR>|6LS6Dw8Bx<^A6w|D%%G ziaSxhrA6@#imNKZ;BvH9Jlw4~1I<5(M{8LB<0Ac^6~%v76la|m(mxr}*Cxkd+=%?o zDvBrlKVjTMseg^WhF^;pFI%y0&GP9<+xk_@^B%wh80i1dph>%*wc+u4PTT4gtt(f2 zG>nlJUvx>^MVIyv&24L4*0wrtb-;~Ag0;fZw*4;tSy()^UyZ`@sQqdb7C`M+qp(D8 zzZ(75;9?sggcDkM#l_R=lhs$Oy?oj7Wcg*S>-oFl^5sdqobdp*dOiL7sPt8<+f>Z9 zeyhDusm1H123td1O}Edq1FwWzsKh?g78*mJM+dHw{O}|czpz|$pJoPL>Ue-m3gelO z$pDJ|4`Pic?wr5iyZKUdX`ZS^W`pijhC1 zaDq6M)gRGU#mJF?U7zP@Dyu)D=f&tC1G`REI907bqMwM-$r>&??dbI>YnN z0G{?>&avJw=QZ81g5YG04rOaLIp_60!#=`D+0Y`?vkCta9pQ5ot~I<@;l~X#>9#e( zw?%kYgzt?o*C{nge?+02vX2a0Zt`~tlka|oj7Pv+cLho6Sp5;L6eC9lE;spVlT$Y8 zB{f-pL`>|Vj|^OHa_TE}oc@SDAw~xoxZLE_SE^2bL^q4kK?W{2d1wzd-0CtNWZ-g> zhxRZ<0#TS)l^tZ@a+7C;{T%;En7ojI{Tu}o`P&2v^=X*E<>A~-9?sElI2XgA%!WhR z3~!bl)>KCJDZ}V~#V|IQbVMhs@EOBgzSj)bDI80?mCjUQCfLCB!t&88VAe!L&PS0C z8eXIDq6l9a;eR$PGs$-hGs(^e6YN0GL~=fuxSX*d82tt5ZhXM-2NbeKAUaI?zhU@~ z3O}rI5^{-?jfSbN>kU)Kw?sO(8a`d}+YQeUW*-9V%oP4%r2iws$0!5FZ0K+eUy0;= z&_GUJjN8EIA1-$NQHGJ9VE6@<|9ysA6?PfsLr$Y?rLy`X+AqdVqv7(bfM~YZ`>02S z(Lo0GK58yHS^W|HQj88Vu-iFXIF;2O5k9}@AOpKjy~+6q*P>%78jpxL`pCfLCZBEc zB?`|p%!j(aFw94|^9<9*ZZu4Lxjn-7MEC*2e2|057yTy<)24oAm|5Ci8Rlc#B9+hU zYLeolMF#e|Iv<^^{)kR79b{myhYJ-?F+dx=?dMg)dY={5#q( zAO9{PuXpH=Xqw59fy+(4Qt{MDXs8XF4l;1L$yX_!%Ic43ndu+{dwpJIay~S|)F&VO zZZgbA$Hxux(ee9+`OpZX&&S2r4bu^ni;;6*@_q{Do~tv=2h>vx^Wl(pDRlT)c!gp5 znRb;mRZr=Yn z#|S^I@NW&jtnj7?|3ifTIl|wM@b3&CQmAZ`RD=GAP7z}t8Mxf!O(uUs;rWI?r0}l{ zQ-(5eiot?toftdFz~v^d5Khg|AJO${?uIa%5n?MxhShDS_w{V(cITmzzA)tzGvom<}?qw}(*Y)>a=i z9b{l{!{J&m5ElJdOkT*qV~5!Ne8VUG){Z;UdeT-^WQYZYE>m@-~wm@=+5 z%osDgr%0bNel(JYHYvIO2PX(@qK{oL=dsx^HuoCloE|Z(Abk8ppE86ot)-npJqz!V zJhV6AS;CECJK{4e=1}@L)kLXD;@;4R!r(y2PgAoqb_q`H` zQdU1fCR}dvdX6=sp=)D0$iU@|Nb}_DWOLuLhY56{7~H0ClVS3?$?y_|oe{n(!uLh^ z+Yx>u!p}zdVn+`It>xBE-bV6S(JIKJU!@ZH3 zqCcWkDOdhU<;ZWy>L){tqkI4(EPEUS}pc7!Wmz{Tzw=`$9O! zVMxrr;ZSD7!KUHUrRQnqMtD(#mqd7Fgx7=ZysnAx)(GDg;ay-m_j@C}H^Pra_~{70 z6ybvrelx;lx>w!)mG zFM~&@;3B@2=~q{QcyD#gyQ|~pBOLa8F#R_oc}cokzcRu{N4PG+Ga}p=;j<$g_I$9s zmPPVa5xzRYn2@YX zxIV(CM|f_8`F*daT@vAy5ndnRYa+Zg!nZ|uSA_44@ZJcsMx*EZbcA1uFz>Xk!<>@i zu;+u-C-2EF&qjD^gu|W>rawE9w}8v@Z8GfnU^wjgV7NWf*%09yA{_R7Fn!iybems} zaM<&~bi$qwhM$Ubo{#V=5e|Dkn0|@+Gq)esLpJ%*k-RR#Ga}p=;jrg}r44&N7+!`B zovUaS*!t(IBfL4nH$}KJ!eP$`OB?olF#PRE=ZOeE8{wBD{91(ZHuds^Js-^Ggh)Oq z!eP$`(+PV%7!G?r7+w_VFNtv2^TBl1NAhbT9QJ%Lov`PF;jrg};d>+fy%7$3KA6ta zk^Cj_;raRxdp_7RG3@zZxJ=I?-vf&8CyujVdu~pRaM<&~{UVb2G{mqa@4 z5#A8t8zLO`e6X~4MDnobgUQ354~8F(be@Xv^AUa}!eP$`OIxC6j-O-L^TEa;tUc)C zgs|s>$!A3J#t4T!A516g`Cxciq!adhF!|MyJnZ>k@|z-gXN1F^52h3Ld@%g&NGI(1 zVDe`pdD!#8v^k_fMi@Otp*e48BV-=n0c z$2PZYpcxi=pzbXY?@U9{`^*sa{$+@Ie;lG-7&}RKu=DsZW0WE2-8MwM9}Q8jit)h! z`Em?8()9Eg4ODzRT<`7DQ|29^ytK zqSNd2;}tyd%x`U)m|7Q#^-x>eg69v<)csW^X0n+_2=b#R(+II3*_Z{ zt;k-sA@3J}y<@~~k3H1m>rtWcBJZJ2Zf}YJxX})LS@t+T?7dygG2#~q#P(*(p8rTg zhl3vBhl=doFMGV#V6WQru(zwoUc1I?{$m6kF4()h$lhbR(Rq)-Uaje2??23**Wc9= z_EmpBEV6g{sonLbTd#1wqZp?t?l$g{J@Rwn7(7vWYMKT5SLwy|j1Dx_?B!!hqt0Tk zM-;l=ane%}3)kZ@m2aiuobT~sjwy@ev?6=&lf7%yW}Ms}VrI@0`_Rh~H$)y}La(gG8+|Rm8r)0DF zBl*A6>g!KAMY0*E&YY1AH2AtpEr%;J6(av^sMCBIcW|Cg(K?4Sx1_uFmuQj0=gX2k z>5fB$@lVV3`}$pJ))25Ye1gK}>c^gDm7e|j759IUVsrJ0Wu(eBxorHoi_im%JjVNer5jV%vs6g zyGpCdlR5vD(>e@VKqLP%eSOoasUDM=qmNsu%$fR|&NTNpd7VV~y*%gty?tX|+*kRV zeH9&Vgfm|7?5;hTNAIrOm+kl+f?&7evHXe}n-}EkgY~S!`c?|NNj9}$L6Ui9=Nmb# z;nVSZ(ocImEPtX!t42+!V43E3ZtSWnyQjLO<2M{`dNrqo0JP|j)?lgc*wFA`3DsC~ zC^I)%e*5?5cj=r$z33|Zc~@RmA0NoWF&5{BgeK&J@YjE>hAxeraH5!-r$!999dKXz zb&{h`C&ax&Iv?otn^uW9Mw!T$GScj^bE0^<7&}YEvkl*&(98IFlcDpF80sr8w@*li zhkfjMIp+#P{idzUm(lG|cN(h%JA4}fQ-)fA`Wsm)`F0 z^!kwx^n0rwMe23{e@D^Jbo#OE4$(tZ@2;XD+8>~j{?woLaO*hr{#}oc5c3*u{dXre zk!2ZV-tzB!;?nUr%ZOWjA3w^75BJ;S1o!dcHE4S;9~{(XUDA5_? zJ+kY*2Sdtp-olSC&bYi?Kj>ZDm#28KuX~Ryd;hATpPw-FNL9F(3%^epFq8i846HL$F=ZnwR^`;4MIS12wm)VQ+Wq9x8^>>Q& zz>Rj`bj>-@hUvPvWE|su`0FBj=Svos(e14(vUiQ_Es`$w7-hM=Jw^6z(6NWaPHt~k zk-ev7?_Mck?=Z33d$!0PbHSX4liT}2k-ZyKDDFY*9U*pmhiM2W)xvdS4&GmlBo2Cn zWzr)}BN0)FDr=WQ?8S4z^UNOo2Bg)Z`<&}KM{?^Lg5JrB&kQL4hcZEJW-lL;|DfV^ z9#QCeOC*oW#~d5|73GWPf>#&W!=L^VHxk(+pV;2ZvWFh_cocj670-43w;JqUE3K28 zCXB=h-%Sy4|NGo`r7&&L+q_`1zD)awxDmx2o^ss}4Tb zbs-g|Lgd?honDu_A3U1tIxRiFOIvz8A11oOgjY1tHOM?yWjxO{{_9WblIPbg=UgGB zZ+$bTrG#7N9BNE1W#!~Wht^dd!L&|QSU9(`>VPW0_1C#+zs|Lk9+LEc(rG44%avc0 zPBo@aNNWE*JAI;Ngfy2_KY3nlb6PW7T6?^sI3%rKOCmjsy;GTEij|+>g-m|ZCe`lIySAR~q`dBtJ-w}o|Q$M(*1`Piym zHCpGi@!MI}dM(QH`L~*9k84@^>ui2jQ*F`&o*r7DYZ?*Sk*J^sm2n>sVVAZ z^Lwr_iJ5f#gqo~P4O83D25Wjn%cM$Jv$$%amP@W`W;NUYvy*%3OakfV4ixujeNEoc zYVAQ>?GFlqaP`gdC@r86!SUjGcFhk#US(l_v-`=Zjr_ zw(ti{zFQ%KNp${E4E39@j*;&qF?msD@+R$UF;7460x^UgaIF}5yBHeGezkP4&&Zzo z{NG`pJpKIZgt0%=I_DfyN<B7 zb;at{Z5Op`#&V#8Mbnl8srEZ}IbfnbCol(0(Q8?Pp0k;QrR{66s{zv#rV-vkKK;)n z3_5i>U|9w_*lQAXAm#XC70q-Gn9MKNTS?RVg1yDmeXj^LU^%-l+FQwMfXnu$PUh-n zrFU@ZJ)axyXFseV7o)#!8hn2$`o?a`gnl`WGdk@iZD!gX5z2&-akr@VI!$ru(WhVP zrnf%d>u+g!8@=bRyT7TTkqM*iIKOCuC>%c~$tt84>Cu_rU98ZF@$_boC?x(_G5S-* z%5s%iJ(Wi@!KUfrhp(y@x zkMvdAoWiZ=%QH>!?(&aKE-X6!H%0O56t9zs>q2?PCl3}Kf21h>FGcZWpgYbdsRz;- z_(>CHS%OrNTo0IYJbk_Dij`NbYh9V2VpoyYwXJUV#Cf5| z`g~UR>Ptgq^qhlVHOQJgg++>rm#v8>)dwp@v<%L`Yy1posL)M^(jOd0N&Eq= z_TP=?*FVxewS|>%@>|u08_Va<8PWQ@J=o`s89k@0TcVHgd3$hq)Lr@9a?r7EF6da7 z#mK$Ky#3J*+d3)wa!0%JYNE{5kSL`}-4Wo0sVRTP6%t)O+ zD#e7li1{AUA_JG3oc_$`qHi)CWMH3*zTM=E`2P~D0-vK0N>%4W%pr%szMeuTo8*k< z>&56W;tzFRBOv0<135BqxyjEmIq5K+;&njuAu;;Mz~v?nWgF2?*(8U0l}&QW#v23k z@P)D+i?)?b@fwY?YgTVf6A}frX^9}WLoPcPu7&$Vq zuYEHOx#s+kzuPeRhO$Y{wSK?J6(maxbA7|xNS%P_R55mtfqSf*V>+}mbSN*_b!f`| zJHro{4l;0$-x;1DfoQcDJ6Xf!CZD8uYO4N-ZV;n`4D7#;-Dz^(njrsv!8!Xk3(n2I zM^M%j2Sr)2&;N5u{+k)<+J8?XSg=Xmx&2^=*ECTmr{R#+`T_1eFT>mjFN*Mz2(JX& zeZ4-y*F<=0gl~)Rt_a^7;k^-lEW%Gm_$9E_$-xM}8R4>2w@&zOV*1%g9=@BHJbX7X zJe2Py%(42lXpiuQ2;UIl?GX;Yud{n6{Jzfc{pc8eIKodw_<69}74%Ak--vLD#zwA# zAFAW<`#MXTw=no$9t$#En0KF1l%&Ms7Y$Kw`w;cMJ48J;ksC_+-Z?})Hqsc1y{`9yEvWoXr zf3FtV+p0o&{qY&Y%U7oc5TEZNrTdbOJNfz6N)Oy<2VRmr`oFB0F`3(AT`}y{i=m9- zwRsr+atvN0xpn11K3VaZ0pmG_WgY4npWR1GykD$@b6rWH7bbCA>Xd*G=8d; z0NT)~8fen51H{RDUs-T%v%XU`>DyPul-=oykzMkk-uaq#;Ogppp=)8u(YJOrkKCyZ zyMCS9-MsVHxf_3%+r6-B=hm);6+3^G+tRRm_0C`BwsdW|r{mS!?u}iyzudKO^p^cQ zzPIB=zAE9{oNdbQaoY1+7$xb8^bI9blBzAq==(-CJFzmE*ib!Mn~Jh+X-QI9IWf~D z$LB=3Jue6El5{4O`QGRf$@h;=Z%dbUwWhk#Ba?SOqrEqik8ZC^~^ci z0a)9bX6ALJORMy)=F5l3Mqg~g=6MSq)MlY2EhlRS-~1NA!`3A;rl$|f)=a6?){;qi z%low(<&^9h6C11MO-~=5Ae^w^YdL-SX*uIicGARbX5J>+M^*D-yI*AM$C8q&Ie4A} z&P-LMD}s0Yn5CmDYSSgjxMz}ShmwYQN$nx67^rWoxvdSq3)eCPI@c0g7=DT&Uf`NN?YpVX4>t*q*5<)spUIz6GWL!fC54eD413T? zSX$A&d1UwuiVPbSn|_tswR(=e_-`Z$-xkB}%{qQm*az_&AMfMaV%Tb~_&1>&EM^~JA1W03(!1>&Y}gaIxXf{RdVLcOKRVc4{p{ukQhPETuam>> z?*$z8w%lC3`$fKwhW!|$y%=Mg#aP!|8)N48Vk~V)n$shEG`!TaBeqlb@UDF&8GSzwH+vNyRa5u1%0ZAj z`^mi7VY|DOM&H@gE%=qG);sfG8^9Sdn z$wK5qB#-l`g&0ynQ}aQXYBE^(fJ~ZHU4AJbh7>%FSDBK%!G)NAaK<+Jgp#uLhj_(i@4)(?Gz`h7Gr;!m{$bsG>Va7 zr&-LoAU{_OAqP|E$S)T|$ic|qtzxLJ^3W!^UWBxpw75~swIuCqG1OPuQ2x4n`9r&v z9@m_qHFlPXA<}|13Jo^7O(Eo9Lh@QGhKPe*{xO6i;^6DV=$EMAzQ*& z)#cA=5?HKga)Oxjv`J*7T_k2k8JorBWcZDoE(Ss$OxBb=BVAl^_o5{f=#n^d5%(>TypAti)bqe(})L&i2?*NPANRgvL ze5mjnpdLSx>Y>r=vr5lz{l&U{`4IHbDSX!$z)t^PDS9Sa+T%Rq5BG!ZoYkX9aio{4 zKzy)JT}5Bcd+7A8u%I1pel-;R?wzEHwfp|&-$Sdrk_Rg30M%TStpAGcb#DEu{{P-j zAo>udJG}RPPS!)(-T&wJBVgvDn=;|cP8@%GcSI(1~L_Sa?YN zi%tchnS9-DN_^U9(wlSJ>L6f)fHFTx8EL`z528Nh41Qxy;6$5s|Qu?S!IwBE$4m!xd?t8%Dly#xsF&$)J|NUB4*zLR`j2&cPx5FSAIWK&b zhIuLEw?fFNhYJkz!Z%52-6j)!=pX~TP5K!3Ti{y}I>^B8w=gxbv)^=(f!$6$I(CkH z2gD9Cu%Dx%Ns0?0dau}hEzUQ5mBJ4hUZ!x3;rkVCj__>}?lO%2ordpHc);+Z3QNSP ztp14JA$GqGaCugLM5l?7^YS;>F!@s4RJHzyJ}gED8QA?UDuh!t`Xl;VF*?Y=?q3nw zn@xv*%5;!{-KQe7H!hHf_JbW{U|&OdpUEj>XnV&Bi2hBCJ~FVcDJ)p{_8sa$I#4(- z!=e0!LpcqH@)!VN$a(SQ zB^unU@STRwRT$32>S~_Jk%7Ig!a15wXk)U24D33~g|SaQp&qP0KWcJh;2!l^CxM81 zaNiQJ`)QCR@6_A%$|9&MBsY4D33LpYYRws9*5M6n@w+FWJ20 zBB#vm`>|1&W5}@?&Py0M>5)^$lMTO1;Y^pOx_w%(9qLf}%Y?&yDva%mO&@!q{hS~m zx=xIJWMKC{3GHgC1ftJ~(Ln}w|C0&A-uHgdbdZ6)?+xwH^7^LfAOm||S>crFd{>yf zkbzx?>*aabjT)z~B5#dvW9b{l{lcB$roP3v>4%d`6;d{@7_9Go+VBdRY zov@cphm#bIQS>)r+5jD}=w>l?kbynl&<5%y5Pd<64l=Os z-w@h>mFG*QgAD9>Wrh774DCdAkcD<)_g!cMhQqT%I>^9&4+}1CUBil z4<--iYdD;j;ZT0V*(j}G!6z!%FGXVa*YG?Jc1GlJR)PEE)PdDLA*#>IL)`tW$kK7J zGmN7)W-uJmTE3_2*jx6wvb5`?wAVy$sVNG+>$2Y@MRc4#O%Qr`Q;6{X^ zNqXolA{2AGaExZLw#eQclKDqT!GgV2MfTRq9(^D7Xi{!(N0Gf>Z)wZto$pCo_R=QU#HxliTC{7u;wE?v+EwBN<@$7SQ zd%r2Nm(_#$B`IOAO6>NIQNg5IXfJbhV7B5;=s^>t2W})HDmgO0X2m4ovBbrcZ&r~# z_8ahza5^0H2;Wm=?`qlOd4)ZldcodDi|pN`@x)UYFd|=QZIQjKZv5xXo=g|)-EQ_2 z1iC~ONuEw_@9&H3;c(~IgFXd4&X?b4QGfMf=my!V&BO4QWAOK+XOG_?|C01(29*DU z4)l`Q%g2)Br~T;tsHl7^RX)Ez-mb)6zF!yFd%Fs;!B0|pp6@F~_V&sidXuD69oeHT z`aLd~d`>sxA*FS4dxViVx$SU`HM$^Cj%3iGIs?cJpe*mu*(?VU8O{PoFcJ%yDd?_Kr$Y z%5J-}?6lM^6HdJDw5o>AWv5Q4-?npV#jIPlr{}ygDSbyW?d9C5+tUY5NpAdUuA)lf zO*4`?bx9^YK__(MPjbQ)=WIGD*-ac;G-uO z!QH8GXO(W;KXpsff?wy<&7<0=-f53Qw_Ikj=c63Jdm-aOTrqw zN-Gz}T}_*QpW8Eh&KqhNT{RQTu`e_x{0}#6Skiv+H@;GE`(A zi&#UTdXm>9~8Aj>n(y?v{@?dfE^E@yG8u<@9U+`6ur^d#}rGeD(By z{=+*PHI33)(0lx`iuYXnKaYFQ@PjwM+x3pV`h|DT*zkk*{=ej(rV&CQUeBR1`5-*z zfc7>Rr0zpQ&zY*rPhZll({rCJewUa|bd?zDD=qoaJrPb7lXkWkLLaPARj~5`g&aqI zp_qKJzg`R>2Q&0Sew`TG$Ty2!eoG|Z9?9<%<}rl+7bE#Mg!L2*HXl$3q2tt7J!Ex| zys1YXca*zT43QSh5T~#5Go(O%jTk~7tWi&pZ&3lEKU{tj`6n6wkgT!ao7o9Ijy z&MHJ_PNW0oIP$r|TnjM6SqM8|9{tF#jpT|3@_L}IQg!({qim?sj%J9-W3HItJ9fC$ zA?$-WM|92?L&(9j0pxTE5OT2V(@j9g!7isgkPbQBi2Lf@Ak2`TGEi?2I^bF{^83XQ zawk7W;*P0X&XKTMOnxVd86sk5uGsT>zc7SNu;)cNp}z7$hJ8Y=3-+l?Z<8~Hp}x{0 z_j2}$hw9^XPTS>LNPBKuYx}G#v|P>QYxu#(;y$HCUSECc#2!;UJiz+a_7zTspxVo6 zpr_+j-}aTLFZZ#&l?&hM2VFL%@AQ32dwGBLsTEgSk)u@K`d)EEQtn-Ky?m_t&ZVD( z-Hv4aChOaytZ()1dN+@@Epdoq3 zpWP=ce$(T@65pQ0ZGw}#Qp8#^N9JMZEH#o3od+U1&y?0$Iibh#BMb97`QsDpXg%$FbT?na~Bt@#6fv^m(1Z*M^}>>!$zxK4%OMx+xQWEf>e% z)*TVbB-!vgd{QkEkJk0z0mE5-KEx}+H#NaPJlOu(FahxwisG5*6ghrGQG7yC9KQ=E z^xshw$3KMu#aRkRCx`c1%Y3=yT(w=|(JIllqV#)<;{Q<;f3ql_(UXRU+R9*WOmZAg z9wW-fZ&_*8_2SWLA1jKECw<<>avod0&hEnH>+Jq4+_{B&+8m*V{?r*DL6O(eId5Tg z^p6a>%FKWcSc?}gUwK7q`?3|wmgjxqmMxzy(7t-@iySW8hg_AEn z8au)DTe!#@?&1Nn>Dxl#fe?yPu+W#MS!l;88m)*nn_?s@3WHTfg+{5WJg8RSzGjmQ zqkEiTY)moCJ#wL8vRG`GzN5`B_vHnKc{yqVkIb+4ah~C&3fCI`hQezM&sKO-gzt#3 zV3Nw}k0_K)HXWB|^Y|nMhM@J`bJs3d6YwZx;^dBK$>R&Ob%Ph(0WK z-xIL=F0C>-We)Xh`r+HY>>vx@_DxRRxy}D!X_0~5CUxpMe{VX-z^+66rfT#@^p9f7 zh79aJQy)UEHxFKjS`Bkf;anuAY@z;5=dVo%JCvEUT+CSY`y85Rsb#zM}^;F)6ZdWG{0w<>Hge2v2M z4O1U~Wte=H8KyoiHq49R6^5xB_X9%zCX??_c(gdh+l=T=F=apoc7L?2aB7nNh`uI9 zCu`XKr#PHCR)0hfiqSy^cAu#_;nZaP5&c+<4l=OsM?Y2A?flGikb&J!Jvztfk7%UM z2Rq2XzD~~!#ogDV!E}&;-H(IsW2EJJ&5ZOvWO8I+*AKp>(x*K@?&EWsFg_{J`^8`; zJuWqTgThsjJhWBQx!L5OQCK2&-y*6BJIKK9TQtq&T)Vl3vCln_nyNn{`XkaJ1G_Jk zL`jM_T+zqH=pX}^oBT$TW4lu9K0#kFIWn;O<_H$P6^HU;P!!5zn83d$2b(76{&D$2 z;W3AI`}=!6yv^ha^6N;Q0J8PBq)&ZQbkb7hP=N%s>bzC2V1^#BfKcWOCr27!s{b^O@y~b z__he|itxP=-W%b^BK&lOUyAU-2)`NOGToEz(=jH(^v^C2zC2d9Gb8!z2nSyt(+R#j zhA)Y9=r7#nh6o2=9@F0*$?u48@Z~X`;LBt9;YjDH2tOa;;LBtBZ$$Fo%VYB3%VYOf z@Z~Wae0dB9UmnAaX`PGV;LBq;`0^MIzC4D5FOT8i%VRkB@)!=jJcc_X`*%e+`0|)e z@Z~Wae0dB9UmnB3m&b7Mc+x$MB>`r#`~Lm&bI1FOT8i%VT&6 z`gXquUmnB3m&fomk}vRjGQ(stk9$}B$#O+;Z_Pl)DJ3NP++}?Rb_Fk4f zo<-QxsTb^BUt|yewwZ3s^oVMalOE-}MkldIa_sSe!^`(ov!@`?Djgs{Coi8qEcd9F zYx>dqL6P3}e)K}$s7yTHyZX`lz1gd`0_~L^=itP(3fD{ZI^2Z{e=NPYUeJNYNY63x zqcq-d+r<6Y5z>>1q!FR$Y1N-!U&ePFqg_`Q*_$tW_%}May`zilWsl73)k-ldc6)TU zQSxNGrf#Q10=Gx#btjn2R$+KfOcM6;(XN%ehrM?7|Fj_|w|COC=`$jCm+rN-`tx@= zts(CKlO23*t(o%ep%61drgbV~bwH2YbNxA9j#K30a^WNCeSgU9{C!TFi`@8!Tt)dK zt1FJ)y>!dQ9ozOd*KMiU(Xe~r?&f=%kH4pRiaUXL`IE4AmRk$^quf{Xxm@%`7|h*kR{=YV_CdJ!0Ccxr(yWnkzC5%~MKM zIrZ<#ZcI;ktoE#$3mUeikFThD^O0RO_ce6hvG>jysdta~@>loPe{=7aRL4ufqar;_ zyOAWU4pq82mC@$bDQ(9*vZAV?St&QENEMkk=kBVxrzRr<6-VAVBfVjJRyMYNb8p8} zc?%_OVR)vwB&08?n6R;V%1_2tJ~r&Jdpi!~ZhR<51su>UjtbQkF^mn7vOUy`|{t-r}tjD5ZBeM>&wxqU?Eh=l9(c;>m1S5x^V z&!jg;OIa_<;pBENbl#kY6fVHY(+HmvsME9!h(6A=e zczl8YyjUTe?n*lO;Q9w|;8l)}2ka-nwD;#tmD#c5mFWO{-R2S2bSl zR~;|sZmAlzv2)pVRb?tCr>zOE7jj!x_iU}b{9$~Zt?kh|TTA10wz`_@!bU`Y-kp8u zU!kM(K^R8>^{5EsVbyVZ)*JQ&lD&$#= z{AXfbd%&-YA@T+DoI*ZE#=V@(Q$px~IWKhl`xJ7g9(5uib;sG3CL@iq~E2%oRh}0W%&$hrR

=3k0Aj=V)odquud3?T>m^BIS@r(&>>=yNxu%Csp_==2 zmFcP4yP}7d2rH`Ue!OaW31Z=aX92d7i|XvQ_HqcpoadFZ=&7C52DScGT8 zXk94QRk}(T`4(jA)o)kmg#1a5C?x)?qImJTNQ^UhZtW7YAJZvOgCPB)qWEgl=ef^1 zNjDW8-&+)au_(?Qw-fd!E1p%jOw2k;7bwni;It4wGGQHdC-lvyrN`~(%OUmA^Yqoh z$>qbGLe`j5NRih^598i1MP4C2kN4_~{ui7)`gP+7wpRA%o6+0JqgT-owB=n|3Jw~@ zz9J!$OZv;C-xDu%dVQSmz-uNIO466BNU&C@kbX4^4WA(c(LI&g&#IdIoB9ouaJAy z_l4SEa%5oN7wT4%)Ax{`d|y=fHN(6=h4+Rd1w{MB=pX~Too7r=S{R+z75=AT>hGtK zJY)4kJ&iKVdlmhluNzWha%5m%H>43edJJ5u@Vp4GHT+S9kguIoBaA)-_O+AVA&i{5 znqina4%b5R3x&DfzLpd33+N*Q`&v%4gA^|tqNQSVkb!*aPrMRk*}3ZJ^ySa^6$W?@$=7ukh!D!?_$IAetifJ*&W8C!u{x z|Fe>XdJx8@f+R)di57}||0=Ni7KQVA8ye=Xp+v~IPF`1)!l_#Q5nU(74l=O&$b8!5 zA%EdiR)0jHeM+CU2QJU*kCCs@5YCapiU?dD%4YIV2E*8L9j*;NQ{+3}@VN?C80Ni= zwt^1VWOIbKM_91ncXOP{k%8Us<|LD2v)M53lxG-jQ8?G|N`=J9m-owa4D(JI&Udna z=mTPOkb&I~Xqm|=GpFtTKC4WQ4D9|sp{^*bXoDD=$iS{sDV(a)AJN~5(Lo0G^>ISo znogJLAOpJ&O##0l>`yg}olrlLqt7@Eoe2uhG|YJH62p|g-LQf%|A77m;g5;IB?^x> zJX_&X!yLcV@CJn+F^n8?|0(jr#|HxYb(|!Oyh$N>c->AhIWn-<8)bEW9NyjCw+QTh zNrDBxlAy1?6bf}dJdddhZ?{>n>L02TyFW!e*viZ?_fZ;RpJ+hTZ0WIy<}m^}El7!JNI>WiS@+hREQwiph+ zErx?{i{ZW4H_Ufj$4^K2r3fDc+r9s0gomPK|K-hF&930%Frz35eIK+yJQO{~ zZ9~!HnK=}_&^JieZr^@=Vi=1K#ol{|sCU^QdgKMIF+E+LK*javdRGZ>n+&G+akCen zZ?oAZ@ayq6(gQal6umJj?`u#aOcUc6P3N0M_UOy~dxv1b9_uj2_UcB<_ggaT9cFsi z`$>_#U5Yb@;pF9ep~&9T`uOvbl(2V%*vofRy61J9t@vIYcXE5if-W_@&<{67U-i^tZ>+xcdy+t~}_|wVD_gs;^MpX)J z0eid_y1m0x9?qAs1GGa2YV$DsU_NHpw!Kf%v{ET zDn!07s#ANWWWgt+qHN2sEyK5zzCOFO=1bd;A9l>airHrkZ_cDvXOa_kK9H)I+T4^{ zt#9yQQ>o@rscg3WtkQ-|`nal+nlIf(0@-OwHkKqUE$uf%jG86nZll6&vLp1mVBSV*#B;?Q2hS5jHh>A(zuXfhZ8TyyC`1fo5&h+*C)YPNh4D_S7A$)ThTJ}ZXQifiDKl!7I-z%zM zUD;pf2%R!L?lPKt+%AaQ3y~$r6ngNyp!e^X_^pG$M3 zHd^_=yIVcnk+UsM>lsbk=~1`cv++Z^PCe(a<0J$8GBW=xW)9Ww7WW+w@9tKG;BsyQ zQKJ}r?jr6A<_I~&m;=mR0r@*8&x%0PZD=egkTd`lv{GQ#U4 zd`*P6Mp$r2G}JM2i>%R#R1Jah{jWt7cniHwcpyD*iJvq*f1VWEbG=VVj~;0-z0dZM zulImDR4Cte!;J_jJRC7YR734hUnY6TniryzTNaltZjr=kt=-i7x$3PduaoI)L(@_#r6ZxP3ReY@g)_4S`IeFcH; z(%-n{VGFuhOc@%*5Oa-`hw{-fy?ncbJ&j=4+n#5jlOMZXg4iDOmFR_bWyi2z+}9tJ z>~67>+ar{T0x_TbgkndCeO{+hxVW!xSEAX4RNZAiY1+)b`}&!EuQfVt#`Gcg^|NU- zV(;sZP)%wpZ>l3tc8?{c!^;j&o>!gpWEA~;xmK^`K9{^ME=x89F3E04O2x(LJU(9U zWBluGhf7k=rz+_)v}^EKy$R<3g|{}!HB1Fj)r~CI713kq@IUjLDwW`GlY`;`o-@54 zANggX)~?g~Pqt*#ignW>TwlOz*9i~&3|Y1Mid7V^2YGu$*!!6gAEgPzEtWV9qnieQ zhInt5PxIEFAtS?EKdI@HN2$dAKC>vqhZWk$ln^gXsue=a7L1~Y2`(uYU9qg~;58~*;(m;AGi7KLL;SW6Jw~&P4>-p4FYeb@ z%D@K6o!lOw*B!_BzDH&}C$NLPN^x<&&TmeSmE6hgozzgT9=w|hWB7jh_35V!eGLCF z4;r0szHN1n;k^t;WS^*t=;IWMZ}BfHCR|olF(Mh;m~7tpV&_92%sf|mKtEL)c3AmG zSFLJK#y2LP+XAuT=ic(K zfAO2yZ+2Fdr;l2`WJ&wa@7%B>yZWIM_ioa{#q+aY?95*I<&6&?Tvs`=YGnHQuP3Sb z>ymO6=S7NBI!FGY!`2^n<*kRUAA9~$=iO4eYeB{Qdp?|e;+%_Tq&NM0F8na7V(i|w zQA-aT{P0Ehlpgl-)YrkG|lO6<2OM_*7e;mU7RRg`U#TW=72K!Sft zZ{3pBr9AhI*Yr~?WmbDynSK(v^z*u^YxCt;dRFphe;bT@OMfzFb(K?fNkTXVEJkmt zm^*ff7@{{_BCavss1QO9W)y-xvliGu-YKTXAk7{zHx~FlF@z5IXfg7q#1L{YGWd0I zT8#V+@oX`2dM~$mrm*KbSAgCWoev5#dIZxuO*Xtu*lpe<%rh9BTZP|e_-Dcp`GVi6 z5S>~jn!t{d3PZoxdgFRoq>d~{I^B0j0c%o#8E{^2M0HdKEKOZSPu&-FWroC;| zn!(?-d#4z9bfoO;mk#UO-_hq#Db$2cAW-kIlAV?vf9qo-y99;w@l%d&n!4_$zTU5W zEQA)%nC|g%K3**uEuOt|dH+6D*a>~!C7p<0^RT}xVowIi7nu}JiSj8L;#h)PdxdYC}{Wjhu%zOdQZssh&T#W^W z6%_Vs-6;IGCg-`roGtoe6y9c-yWy*bmk84{pu_m!NyFUzzcq{wadarxY&8`ya@N)W zZqfrpKKm_VsNxnH(9oobF6?gURVlKW~`c?molxh;`D_t7stBB_UsA;Bu3H zC(`F><7+v7&*aF!<#ey2=S)sd%w9Lx=ZOCz`E0qBX_6?t|xa-V~@S+GWiSWt@ua7Wo#_ep4Fzv?WyCQsVg!e}H zu?RmM;g=$OFv4#}IDC_^^BSXi^n9}so*LocYiRo1kFMVm;R_>tNrc-YydlE0H@6df z4b8raIV2kF-Lk~2@N`#JgU&5BnqEdBG$S7r{$c~ta5waagXy(MuQ*>H4PeL1H{Wn0 zLQ%aQh0Nda4n>uqM_Uc;Ng-_#`iyjH^Dz7+FLb&5LX$m`N8 z%d3RHer%&+l%X+5)!*txk@8T!cy93~#XSwGf0VsDB7wud)3MmzB-uj`dyM-0eCZy% zjs=r09r!H3$n6m-s{%Eu()gxDx90XLg^S-oS7~VYKp|Ck*-vViKE3-5G~2zdOF!?R z4bx`~d0m$~7>??s@@=d4JE#}ul#XOub>+q{{bXDPewLw=)AkLv*i8{Ye$^4*5)X!IDAXfjs;pW;`y@V zxsjWjwlBErnFogLDS15gYUzIu&o6UvPj%(yrrqV+7o7Xi;YUrE`HEvs{YL7!kxx9) zen92SizZ66=djKv+Ry#q@0IJ!vU6`QQ5L1zh3NZv*ig%CPUH0rnI+MCo;~m-KM{NB@RtvP`kDaFNSpLj2mSenH75e)TGFlEHcN8sX zw)hNjjd+WgRu2BO*xw@<1(J?Cg&x~==&>h@(f=1QgbsMR7rU4h!?O8-jCyFimdC;EmJ9#KgAqebysi{kka zw!d`ss#E`d*RMifV|RN2n03YF%U4{yc6I)`hPMSD$+ul@gZ>~;u5+(i)wa671j|>q zUe@NmH*L#iYYT?3W;Eldf6)0nDhB^l;Yo&1R@h*e)=C^5@cRsNXF&e$uvi!!2+U^) z=u$Cq?h^WRUq_d@O617E`pV7REC%4tYr*`oCeEnJ>xUe}_80 znk7@Z1cQ#R&N4kcRszki&;x8hv@@>VV0srwr#N5lRdpz|FTzEJ8;OWI)$karGTz{_ z&n#KKjmEuU{uA`zS%rL5;Y`I5l}aqSOOw!9z4am2}pvX zhKGpaFG)xu(Fm9X1WPR&k%wXpXcc_?CinuC%0sInXric8;V-DISfv8mYOPjOY7w#f zf8X3Wo7tNUyGZHp-yWFEH)qbAJ9qBfGiT<`y-UBTgW}kKE(0^1f5kZ<<%O0Y!SfQ5 zygR^P9eg_DAS%WBuztrNNc|oLmNI~9MT_^AkmNOiVe0FMp^I|Vm#Fio-|1iV$I$cy z_(GzOF-I*S`lhVN@rXnp?*-OXt{I^9F%r)ZXcB#WbNV0Fv)i1U=zk}&8s~Zh{?!8Q zUwN%@vER$T`lNmT>Og|+Om%}ZwXxIc#ya&8xDW@cv2NUXbg;5Ij!TKNKXkXVlXk7< z#VglJ`)>bZI#y$pW3?*I_NzAk|Mjcbv3Q5j%V!VuAgn}iYy4}(B+3x;PNmE@2rNtd zEdoj1Y2U}Msz|mi~I-pQC-op^rJX7(vQkUEJPbHN)m{f zE_0->cck0;QK~J&`A>EutF||mO5p_Q)R@*!SsdD5ata>GPkJ2neFcHz{Vv3PH8%Yu z_CE2Gh&g^yej#E~w0;tMJ?*ymC6W9j9;*R?$D*I~pvJsoSa0%&A%0S0%JZ%eKj~@B zCy!YCq!%=wcNp(Nsn465Pad)ONxL+k`gzU8Px`ColSllY?kAnawn5+!Mar|I;9nUZ z^T8pOv9vw8LdfviMUvw^={&AjNo1)$$9Sa1J|;ku(HX<}NmGzVeO?NI`XZ5GL-m(= z4D7$ApEMfGaDEc+6UqzmyV%NlYlh^_MH*GIe44)r{hNJ}e$6om%wwAC@KOd){mxc6 zKWQ=YOnuv+FB(6o4~ClisaTU7i!ri-#P@(%kw|_L-<4Y$EY(`~zBz*qYaJ>(JNi9t zlUX+$*X~{A-oK&=AsFgIUerU{+p|1L&_$uyEUYp^G%bLtdz^oW!lIjKD zXAGZFQB%vOTy3`~w|=P29KeI}haSRz2+v8yde|)#ZckZvJ4>}9$LO|WtUldvqpY-q z)*eM7Yzy0D`5--vTXLT5UVA?7^AEO-SUpznN@fvkT zkTwCdUPGXr6B2!ld8Y}{&aXy-IKBC!6cp1#zz_i&gOvBz7)b^W;=3PN~)lk6_;r4l)fLv)} z+Or-(Y1`inwQp<{r(shqrDttZYVqrillbe%WQZ#9(b@OuHu36ri;$KwfNCW|>dLg_%?CF1t%g3zQJ<`jQNPnRzXKlUZ9<}tF;oO;8W`JAEFkm&21o2TEgw+(be+uVPk-h&(Yo%}95adeze+q^kn>cAL&WzI_h@0_}?i+U`7 zaWcMr4}-nRwYD+F|93*Xl)uVVU)H6*?BunLF8^2LsTc2$?hE(HMjb;edRA)}WfM}4 zr^ib&41s6tK`25XsV;W7PHIgB_IOR4sy1^)Z}CG*9jWD){=t*@A=>>@P%kzdjFOdXdbuqPtx8HRz+sZi}n0fZ-QCM1AH2s>Y@~XO71*NqW)m2j}V2p9|HD5mV|aYAW$!LVcuh!L(~ZkIxcMPJ|C0f#l;$jQmkztt3ZO!Klc;Cw^J zG@}xhcc`7(b&if-FmuCUjDg&fWB(9O%U=WAKM&a7qWRrr3HhG|Mtz1MQ2m@vy#I5K ze|O#;vo&rfUu%f)iIX9{aB? zp1wVhy*+T$XGlKdyk$IfgBO!!S&S7%>_fN!f!*O&1QNS6abE-v!UhBq`NX^;SbuNR)|sV|`8C#G0wor<9jR9&`=SBOPg5`z`DGLmX?nuwt4xmbxW@m}y+YATgaC zOxgK^9O(k2eF(IX9J@y$7Gn8vl0eM#<&JddoUW$sy6JUQ)fmPnPOPpgugpPwWrZD= zmY0`T=8~Z^YNwZxp`3x%Ygk!YR%_wA?o(^5NflMqJRCbA$76P&VvT7tSr_tIuZbF` zA!Y|9pKaBkad*V*pyZQi2#9Gr-3YSxxlr@TBbL3-nZUB|ehe_nl1H4#$ph$81oB@* z%YCd_y@*HRgWm546v>Jgr$sE zQnbEKE;Pj;m_CQ8oOvYPQ^XjG6tTTc5;+b+?8A}dxEAU>?j}Pa`z&b~0LI<0r{&oYCeL;vaq*(}pndW;9JTD>1s{_NV z-{Yth>*Mrw=7A?=sAB>WGYK?Fo*!vb-!srhxk$dwn@~XiOi1)Gb_o-4ITAiFBl$WH z;sTZ$$u4j9! zy(D4nB^?&}pKbF0flksA&xU%>#yKn9&osSv8MYuV-WpK*pn4XJMg*h=v@q-5rzPM+HOHcANmiJ6JWpCrk<~JH+9vS3%qtRNrc+SG+Z98z7 z#HUYj&AZcjWQZ%_7Z!dkaA88i?N)v4rS*lK>tipeFHA`~W0h-bhxOa>n~zI*to{Ba zovr#9*WSuW4V9A?t*DP3UtgGN%dT=Iowc>Y8I=7wWo=1{6$J&(*8=lH1WqhE4!_A+ zn7kx*T>bLnZ(gY0-HoZgB*9kiuL>+dZ91N?q%g(3pfR@NLVsnTxXS_cvz+nuu@g@G zxI?|CWr^p7<(}rXp6Bn%il4tM;pz3Us}gTLhCdA24Zkg#l!%M8`k7`ZL*3R~67WwZ z@L$Ql(6RiI4)^ZgJsR$E_p9k8`^%WIqQ~M31ITT-+r6wuLZY?1s8wKsE;$@_K-E04 zwdNgO!OgsAW@{${OGb5mZbKoPwiN;a!+Grb4kg*yYFmh*f(d{o`SLs zqs!EVUY;JeHa&jHm|kUL66!};{-LUh_TK{H#(DK6xYm`+&nvp>>ZFtBM z`Kzju;zx9-I%)LK2{_}mo)?l%;7P}?{#A1^ehf8_h1Z-M@aGbY>KZ8SbfEYIwBY$@ zGLNTaMsnh)69eF=SGJGwG*);forI}?rJE0{aF)BW8QT0AN>zt|-$RhzL8(fisu@-X zPh+Jjh_7PW<^3^%#jZdSZos5&_O)Nwp{hsOB?)DhbSNuKE-UO@R+v&&m|9ktw(KPT zaa4)!Ro2-WH~H;XCI*i3L*)j;BFS8Y!)FY9CB~N%BT#NE0?qY@2qX?U#2ivQ2xu&| zjft3B7-%txCjpbP+!c+(~0DK@ZcIpY9>?c*IhNL<~1PH2M7Ocp734g4^Jy1Jeto zir&B^$`i|+EC-nGJj*Ts=CU2}L%@7gLHsVTobLy~)W`H6fvKM|dx3jv+?^){q6{(J z2g+msOWD(b>H3ggZpcSH7nBVEwG~rp@i+*-Ygbn@wL0>|u)T6eI!o030H?!?`ont; zcbcl1{U;ewPeMay0rEbeKp9AGOjX5B;Xv_g|lg6(hepO>$AEsG<%4{|G z#XN2*{(%0f`Q#DH`hiRLB@U)zktgz9HJ?0Uk?*Pb9B{`Q@@Hv2dBh?w-w|LxoU3Ix zK;Nt}2i9d8bFjWwV-D8*7Cz65_%V$+aC4xSHJ)E-K6%8l##2I>WAO*{f|e=KSk`sQ zk&a2jAJEHMhCJd#&EKy1yyqq(FNU54=ntAt9&w`PS0gPl0WCuwvB>aV<$3Wwokn@x z$I3LHJYwl%b(GQTPm{F_dBoDkW>QA?u_;=HJYwl%H)=kekVcK^gv@5yB>VwArupO% zOMA{iTH2GpZNmDKM=b5RMf2&*@Ef$0p#$@Y#&lrjQjb2?*P2fru^ejwW%RWRXc_W| zPr6NptN=FsmiFi-9u@>^1NZ0~Z;1tbt1mTy0== zz0f|Sxd?LH1_R$~;1vdb(!k9I-eTY#2L8~%dkuWhz(3^g9dK<;+Gn+aHyC)cf!{Ll zE(3pRV4QkX6z!U&589wU;+o}fEr+hGC{M={Q3q)xLPvy1a-3K=k1NhTvXo!Lc$CII zCO{u!BTo`$C|tv1H^l9sd>z#JAz=Q zzk(dkOGxs54hHMs(;53PTjc%7`k8x<8j+SVfY!GtwBKL|ya#!vJ~~~LqduBR=21UQ zue33s^%dG=C4nZ<#~3C&NcZC8OTdh@W_b`N`)ROLYu)?i<>>vLZbh=^sJ%7I9B)q4 z-%u#$1;t6(eJlJo6#DzERk0i6wkEvM(q&IentwkRAXb$A)K#!)zo#X6!}?cJt)4OV zUZWMI<9fx_$8=xP_>WZ`HpXv_zia8SoAdpjvB=6&eDBTLk&ldy{!eVqd*>|}wJLUP zOWM}>oe6tdx_sHvy>Q@~q)umc!oteGe$NuFyy`NHGZAE-T{=UJH&aPYgCeP)EIKO6t^McSu zaekbSScv>mNg!tWT1T3;NQn8)>8qnjotvWCwt4Gpbq2pT#!rY<7{9<_Oo7!;9oy-$ ziE4feZS8%Wz?i?vHLlP+D;w3OU&T&%na0(KN#a*=9fC3>V)3i`0+Y|q%nmF*78jw& zCy!WsEUr(;u`bY$LT?~GvfxQMk68{WpqM~U1gQ3ty!@@;DlANcVvh#>XAW%-mr1s=4f(+X{k{q9n zJCC~nJRx3l(pUsy9|DQ%0Usjfc*!Pb9^3y01nOfulQtk^C}jU(9>aVD(?570>2UtR zGA)lt(Mc%G^Ae&i(j5q9{dOV2`mlbcfABEUQU=gE4GG!{A<1h3!_@aF5|pF9L-_}H zLBSKE46*2A>@xU8On@25KPW;wKWUT`eSP!#>+wEX{~#~w{adqnL2+EWZ?)qeG==yF zp3;u?UgQ;}a1b`EAAW*0E5_cS1@GWydIziGH+I|_-(%@&K$h`LC;HfH^HA;J2vex~1WC(=>|Q2mBj1A!*VTa2`+ zkA4H?sP9mI1K0HL*SZ*qKE^JCKb8qFBl!(u(a!f6y9fadp2RYz2OXuC}@0CuzSd+6_+>+KVwk%yz7-qENqG@ zeAhMaVk_wcE6rz}+ce%^7@sgZ2TDd+w_>>B=vHM@L7!Yc_@ z{!@YC%mbF|eC$BUUq|fbMv~asnIjUo7bV#>&uiuXQZkZVuk^N>9t-$ZAE+-)-BX%^ zCh$K&FU$W+{>8SI{QCk{<0m7U&>W2o`^rj_$1k4Tv?o2KK0S5N_N2J8kD0amn=h8T z;(Sl;ee~hPN0)c9mNebp_*lc;j}|}LP}4OIdRKLTX8%1@*s?gVeMz8TlivSi?}GB* z#jM80eT`2yuU@_H(MJHG9tU|k*EL}M=?|CxcUgM!?1F*YXYStr1=`_w46bJ+#i4=D zeaYu5@ipw5=d$jLowYI z1I}^HyFV~6;dkq-tQLHu`s*8KBw%=7v3O&QzmE0UGI#}lVqs;%oxlHX@x*|CLSX#j zq}Vr>CwIT(=Aqp(23(q(6KAc8Pt5)O(BfeY$+-S1pL0e@q4TDXm?8Jhpb#$64sO>lZw;7!9|2K|@pDz}hbHGpy76 z*@5}p&bAU?LuJtk`~!LBD_yATJlZilF;zKh4#ehnhAc8;V>%ai<@O}V%&=U`%dGQr zyA8YY_W3*f-Rpat;u^j!Ft7k(INjhSL+WDrUR3xpP0|I zD3fRKiF;`N1YoFA@|Ocs%!6db+H4x$HP2=I9tbGaD*m7awFnVP=}Sgr*hDvF+0fq4iX_t#p6 zX})kpBA;0L!ybcw*t078eZ`4*L!$ME0HU5>g)MxfGc8^&p&#O!EH9rByBy~bLQJ0} z3B*hfbEHQ((!9lmD9_1u2|~TP#PMyuJL^ZD5akz00x{EfIno;)>Ckyym`kWOLX}s| zs8#RK;02k|y6IDDtI8{}E9#o*)%L43*eC^Nd1Y;t`dthDc9@#~$xgU1dM*9K&FXEd zN?W}3ZJgHGqy78frPoZW#{07~s)tU!W*Qd!%c`pDX4!?*YH|%WNLAFzuUp_8mDywi zXp*&9wuj8+R%*;!Xp_dg#a=V;b_4Iwn5N^P#x#>BqLWf5&5rEhi37jC=F{}iEK-J5 zI#=T~;43ty$-GA6?!dQb3~0Thu^ae3joI%`#qs30+>b(h4&tF2L&O?y$js1~=4*k* z+>6qnG0)|8jal}6jqgYNiN?>PeQ4%*EVl8N8dLsn8dHx8a+Jve?yfPN+GFci=@9@omHuNV2pYkV?d{&oWO^oN({DG*0?1zzkS>&mGSsc4SLl9)&2=`*K4&)Kb zz7Zc~ctJsYpM^3$jb+`0Hci$__GlUMh-Iy$h%#yT1NvCYkVhn5^ai#)Yo zixW&xN1QY3K(`@LW4Z)PlTX(oUt`{nlQibu!%B@mhMuc6=Dl32G5d8f@?`(g4Vq6L zvFu-xeOz=Ec>nOYbTb~%m~O)h1|CbDC*lw2b$B;fSQ}f9qmOW9I8#2GpGUO49%p;o5fz%-_w}(k0fhGCT8~%eisAN#tEOcK(Ok!XmgV4R~oB!*0=ux1}S#Q_SO~Sw3c%t$mwovov-E4-4*5OSR#?5zt<5Vk{qW>&iaXKPn}Y~aT@!` z1Z}}&_zv6yG?%;;D98J&9)bFJACXQ(MurX5U*<8~g1~8r&>+YSlmueRknTee%=ER8 z<9P{5-cm4F2cOO;!NMNv<6M7y6g(+I(OD=&-$h9B9s2QvFBxQMX z^SIeODClsGdn9EKS?7VMNVMNF!>89L%I9MHg#3J|{bkA!R>&u}LZ!p49)wDi%Wr3f zvfJ)hYnyLBtq$e0;zf+O%+B$pR^+sZg5sO=h}o(wY+ z6sx=w8{6q)TN5h(IAuJ^EP+LhQ474SBC%?P$4t}IwoWn#>jvvnvIr8mw)GGNrVV_AhL+(Je7HgWS zkfq%=8RoZi?Jq61azgA^h?=%J;mlb`k#+@wMlM7=l-t(cg$TEkO}1^v+DIGhw)QYY zx}9vY4JER4?qECYWr$)&9(oJx6FV4)R*vgSB;^RY6;%C`eDWxosQJtdDXVX5Ro25? zTRsi^U|G#)UPxJXZjdUQX=6@h(!eK=I8pPN8&X!A3sv?<%(b1?H1Nrz1DmM%%nK>o zi5z|0EE`ik4g6qP&1Y^%Sv@SMEGM`iRkpW{Rax+ZWi_9< zQkLzXriU9@G zK6&bVHJ`b%4#P_uqx%NyA43L+wkAejJ2$bu7Hn==lVnw7ZFwNEK6f9nK9?e5T{pns zaoNOD2X!pnw=)gC(y#eL$k+8$IyF{nh8mX`GSvp2NvzLnu7Mj2e6N957+9?t>UL-* zU*jzX-eKSm4ZN3Fx7$Gjt2IN-PlGKN8MlG?d_mfIpn-=OxY)p#8+bA?8yHk);5i0f zWZ&UfuxuzQoyxG8S8F&}5zV7N>MUB;(p~h;>P-C@bsIgiz)cr)Q8EUN7 z3^n#)HYI&Ytr==Q*N^00u+KX3-)Pql`#}{>E8;q%>XT8(O^QXY3iGdJXw69 zPSJO@L*HgYAN^|4cdJ9+T;yd^ktWf%z@aY=HbKre4YHzdjYA)8XE=S2JM{SseH@!a zAHM~G|H1a#0_;W*lKQ>u&^O!A$MzO|$_^nd*Y{IkH-eDp`^cg18ABiYgXl}3twCD! zv46_~4|R&Z7|0R($OP>(^mRvC^eJ11wCMA-qc6ju&x=kc*Ml=i(O2N8AML*z`9e~^ zvmN@X4SjA1i#|U0Ht!>j6(ynsa?B&~`6;q2uS(&)Yb5Bq2r-Z0I>_naT4ixAGf;%I z43i7BKFRtDa=e#>sEfpBxvY+l3D63h$4tb$FHc91^SINYk9#c25rjnFV(?6TZj3RM zqrM&pqK`hW*cR$|1R30CAtd@38*HlxC+|hUGr;G(m-=|W)2%S|&4hpOwkScKz`p(D z+mDXt6WQUv6`9>H`rqMM!3rSI26E?z{0@&w;!pp4%Z=aTQO^KYx$Nf&h}F+MU;+Q= zuCl|P1xZ%8$z#JX=1stc=7l1ujv4JrCl(7se>EoJ(qG#WM|+*i>_xmxW^ZAdJ*kzB z#_#md?n_hiog`^$9+iFgTmnr@^JR1)rY&cq$x4NjZ?9h%uMD+9W6u~S^Xd)cF|BVI z74UOi!XFy)JL|e4;umvFl=(EV?1f<~iqEU&nwWo(M=X0`*tNvZ^#D^3dBpO55MXeA zV`9~1Ay48sC3B!m12Zogiq?l63}w7cWQ?)z@75!TZSiS2=~?Z`4Tl`hH@(71&xahb4?z(WR|C+mVpB1X?M5E!>O~-3fsmn){fBuB(-F)u#*cKkF=oD&N2KUk z{8eT{r>Tonk6_l1dvsVI){k~u>URgQlmWC_5Tve5OWtk3ratcHp&a#bE-(6=W6T%G z<8yl<(Z|>&OvDXf>_yD!k?2bXc8)P~Q0S-N3yHqI{n~kVx1BL2C)b;2k4BMydtnYc z1J2j(Tjm%u=N!Io^U~iIo$(gI-Ho9{2<9e138fE{)!mXWBV-hlA;&Wr-@MTw!e|KP2Olp2YeW{g^9}ijk zR~6FV4?v{Yb-;@4mA*6JYdk=l+sU`?fd4)F%>=7>FmgMkzZF1DtlX~YZw8PH?6rD9 z-v1`W_^DQKyq(ft3&iGITEN@O_rL*swHN~FF9%Q^%H(uPZ{hb?WAo#*n5UO-`2qh+ z_Tl1+dmV7QyOPsP4$cD=NRH*n&PYBk{fPk19oUL_eICv$=77(A;HZztX4jSxIgLlp zf=NV7HaP!ttVIp~1twd(81fi1HlQ%|^zsK5?Sna;_J+L-O_GiK$mw z6*H$*!mor18H-^Ag5&8ZjcE^SHD*U)#}WAkjcJeX)tGf!Z}49-_`41MHwK@3eW{ar z@-+suF3@-kVvbXk;qk|6%=)NKPeThTL!b&F?WS zLmsi{aRZCY8^F{<9mLZQ={G1-~Ukr`s(A_KF{MW)2S)dmKPilX(M206-G`mED;x(a#J$95(yLL$S4>M!#c zc%PZR(-UBZ^PO(e@`x01{ebm$~V9mimZdcDk<9Zi9J^jb!zrNRZ z>$@+E{^e)CFMmDo&5Vy->9=^3UCUB`bmbE>@`i1^_`FpwZ5aRa?e|al%SQ)hZ~ogm zH)eNoFHY$a`^a^DDxVrVCOw*Fz*Z{GiI+^2tD zTC(ul=V~5Jm~>lBn(w|*p3@)8^Cmod(uJSD`j5-E{C4M6D?fN<=FMN+Gr#t@vR@Q; z&wrp_ugoFzpJH7(J^nHby=@JUQvC~-+y{T zb*G1Je6q{D#dr1j;E^wfG(WYypy%rYi#xs3t#az;H`I;%?!Rt%%Jtx~JCo->@zsy^ zY}}UD^3tI5vbJ{}pZL+wrcC+U<+CqHdgZ3KPyORv`?BUVEgt;9y0yd3+d5%f)%!C` zlKy;3P2X>SeCzMxUcLWsCw{oH!R>!;UC#2CFB^IO+q15iyu174W4}7*`aZ{fH2>@F zn||@zUQG|I9$3HT_Tg9m=K8TkJ1;Fe^DjfD9rw*?xBWfgPxrl*_VQzEJdMvT@!t6A zPcFRbx8pA#@xj?w_5R}2na3aH)g04Qi0?>VOqOj%IE3@IOmNZK%yLRCj|1x zA&}bWjC%D)?%623!&cFnsrX=8X&>D1V@u1bD>AbOP=ra(p! zN;6H%*h*Xj(k_Q;&6p6&PI=SPGi@H7X|qY0)(=1RA=>W!Nm9=ySn5Iq`i@qsL7YTv zw3Y1|1{>72>++13_LqJOiR&}OJi8GZQ$JlU^4S?UMiH~aki<_gF~=R@Q%-QEfur_A zLYt08$BGR*lY3w7Dgf=fi z#Onp9RG#(bdC;&hkLi06qzs_-IrNnfXp%g>mul)83wT$Fxu)|LGJ1h8B>Jjq@rG?_wf0IP`z6`lfnNGA zZg*tA#LJ4Jes&+sLj5}NS%J^vEym|embjC;B)J;0ZanG>5Z(X?UJNQbjw>@buUdJrH}5{M=5YGBsEhd@d~ho>CJ zMV=q?c#WJkXES(Q!w?dEjCs!paj(&8B&b7dVluGP=InzUufLG!tDbsg(B9CJ1m{9{ z!^$=;_Fws3yX@TPzY)EH6*X**JgbD=S?;myx2RUSj=ITV6AOvyA&*VKn9aqSrXi3H z%M79Y!k8e()_f&&Xd8rgAFMvA7>rjt21%|ND|z(ok$D&JGOA|bp%u0DGZWQ)BMnOX z#D*18n4L{FKBL``tdx9t=EpuvACW{X&-|DcJ7(hc>|3Too@F9Yw6^X{DB`tiqhwCqFT3#cz+h_!yhZliVgpi?-{fBuB z6A;YaK${lMCSIfE5h;2Af6ZP)U8Knftb-4MRE_$vKCB<@melWhU?~Hr)>x&kOiSK$ zU{l{r=%XC<(awrKr;U9Vc{d^mi9W_q5u_3@wjo77QS>DPJ8dj|EOYD;edy=eUc<^p zVPgl<7DwaPt!71ITm8C!QyZJ@jmpYW?BCzc%00q9NIPrV0P<*9(cxMxEt*BKw6*>J zWNZ84ba)>{vbFuR9Bu_g?fI2wAvYRZdoJXh$2HrTHihIvFl|pU?#E2#@5cK zB27|v#!wNYaxk_b70K3e&pF3yA<;K&YV|DqP(sk!Mzpow==a|BWIuqN0_d3Zre{RoyIp@#q`HIt^ z)9fF#U$mP-lE?C5O&wcLj8D6SwERa zFzuXcKM5Z9_y~zU=UjU!^ifXrE5tk&^*QbFUhr;25E6Zip(03iU~EGwlK(dhg~H9X zYp<=TGYxXk9_O~Ro;e_Td;BSUKhyt9)hk?W)-xdn9^b?GjypmBWrkTgC0kQGMDp0k zjFS=A*fPMI9VVPSFDm8vaF=vgM!akyuZ0!*)fbyZ_tNUaNAngQ)YR&Q&sL{XiaIU6 z(s#rrKX`BOl-k;SUgTB^_q@I}m)$`un9XgwPAWZJ^3C4P@?w1@pPo%Cwf$Dgaq2S2 z4S%3&H9dsR#e1)23C=8Z)W1y0m8a z^eL0-s-{jEF{QY=bW(*oDb`pn~+{YL7?HX`;RkoYZOwlnQH%QBDGV;X{a9&BeZ6M#yNvD1Gq zHuSOHqHhi`^|2jEmmy>*WdC6v!$JgeeB_>kaO2}WS{{+2r|>sfV@A|Px)Z@X-#XNf zeT?VJ^OX9n0G2X<*2@Sb1ezr80i;cRi=eLrJnG~9DEge^Bfn3x2KhpwkFjYnc7TzL Tz_vcr9)pijXqYHNEc*TzaEcsP From a243aae5bbd133375963f8f2890847d26d3e0c75 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 18:14:59 +0100 Subject: [PATCH 062/104] Update Bintrayclient to ArduinoJson 6.x --- lib/BintrayClient/src/BintrayClient.cpp | 20 +++++++++++--------- platformio_orig.ini | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/BintrayClient/src/BintrayClient.cpp b/lib/BintrayClient/src/BintrayClient.cpp index 955bca20..34fa357f 100644 --- a/lib/BintrayClient/src/BintrayClient.cpp +++ b/lib/BintrayClient/src/BintrayClient.cpp @@ -113,16 +113,17 @@ String BintrayClient::getLatestVersion() const ESP_LOGE(TAG, "Error: Firmware version data invalid."); return version; } - StaticJsonBuffer jsonBuffer; + StaticJsonDocument doc; + + DeserializationError err = deserializeJson(doc, jsonResult.c_str()); - JsonObject &root = jsonBuffer.parseObject(jsonResult.c_str()); // Check for errors in parsing - if (!root.success()) + if (err) { ESP_LOGE(TAG, "Error: Firmware version data not found."); return version; } - return root.get("name"); + return doc["name"].as(); } String BintrayClient::getBinaryPath(const String &version) const @@ -137,14 +138,15 @@ String BintrayClient::getBinaryPath(const String &version) const ESP_LOGE(TAG, "Error: Firmware download path data invalid."); return path; } - StaticJsonBuffer jsonBuffer; + StaticJsonDocument doc; - JsonArray &root = jsonBuffer.parseArray(jsonResult.c_str()); - JsonObject &firstItem = root[0]; - if (!root.success()) + DeserializationError err = deserializeJson(doc, jsonResult.c_str()); + + JsonObject firstItem = doc[0]; + if (err) { //Check for errors in parsing ESP_LOGE(TAG, "Error: Firmware download path not found."); return path; } - return "/" + getUser() + "/" + getRepository() + "/" + firstItem.get("path"); + return "/" + getUser() + "/" + getRepository() + "/" + firstItem["path"].as(); } diff --git a/platformio_orig.ini b/platformio_orig.ini index 3e6a6b9d..3b013c97 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -79,7 +79,7 @@ lib_deps_sensors = boschsensortec/BSEC Software Library @ 1.6.1480 https://github.com/ricki-z/SDS011.git lib_deps_basic = - bblanchon/ArduinoJson @ <6 + bblanchon/ArduinoJson @ ^6 jchristensen/Timezone @ ^1.2.4 makuna/RTC @ ^2.3.5 spacehuhn/SimpleButton From ea24958b50edc1c1a5e9724d68785efcab779a81 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 18:15:56 +0100 Subject: [PATCH 063/104] repair OTA for battery devices without battery --- src/power.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/power.cpp b/src/power.cpp index a1fbea58..a3ec4869 100644 --- a/src/power.cpp +++ b/src/power.cpp @@ -251,10 +251,11 @@ uint8_t read_battlevel(mapFn_t mapFunction) { bool batt_sufficient() { #if (defined HAS_PMU || defined BAT_MEASURE_ADC || defined HAS_IP5306) - return (batt_level > OTA_MIN_BATT); -#else - return true; // we don't know batt level + if (batt_level) // we have a battery voltage + return (batt_level > OTA_MIN_BATT); + else #endif + return true; // we don't know batt level } #ifdef HAS_IP5306 From 866d0bd730f23e29d159dc424ae5c5b8adfc5cff Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 18:29:10 +0100 Subject: [PATCH 064/104] reset.cpp: repair OTA --- src/reset.cpp | 47 ++++++++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index 472ed02c..89720993 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -9,7 +9,7 @@ static const char TAG[] = __FILE__; #define uS_TO_S_FACTOR 1000000ULL // variables keep its values after a wakeup from sleep -RTC_DATA_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; +RTC_NOINIT_ATTR runmode_t RTC_runmode = RUNMODE_POWERCYCLE; RTC_DATA_ATTR struct timeval RTC_sleep_start_time; RTC_DATA_ATTR unsigned long long RTC_millis = 0; timeval sleep_stop_time; @@ -36,14 +36,20 @@ void do_after_reset(void) { struct timeval sleep_stop_time; uint64_t sleep_time_ms; - switch (esp_sleep_get_wakeup_cause()) { - case ESP_SLEEP_WAKEUP_EXT0: // Wakeup caused by external signal using RTC_IO - case ESP_SLEEP_WAKEUP_EXT1: // Wakeup caused by external signal using - // RTC_CNTL - case ESP_SLEEP_WAKEUP_TIMER: // Wakeup caused by timer - case ESP_SLEEP_WAKEUP_TOUCHPAD: // Wakeup caused by touchpad - case ESP_SLEEP_WAKEUP_ULP: // Wakeup caused by ULP program + switch (rtc_get_reset_reason(0)) { + case POWERON_RESET: // 0x01 Vbat power on reset + case RTCWDT_BROWN_OUT_RESET: // 0x0f Reset when the vdd voltage is not + // stable + RTC_runmode = RUNMODE_POWERCYCLE; + break; + + case SW_CPU_RESET: // 0x0c Software reset CPU + // keep previous runmode (could be RUNMODE_UPDATE) + break; + + case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core + RTC_runmode = RUNMODE_WAKEUP; // calculate time spent in deep sleep gettimeofday(&sleep_stop_time, NULL); sleep_time_ms = @@ -51,19 +57,23 @@ void do_after_reset(void) { (sleep_stop_time.tv_usec - RTC_sleep_start_time.tv_usec) / 1000; ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); RTC_millis += sleep_time_ms; // increment system monotonic time - - RTC_runmode = RUNMODE_WAKEUP; break; - case ESP_SLEEP_WAKEUP_ALL: - case ESP_SLEEP_WAKEUP_GPIO: - case ESP_SLEEP_WAKEUP_UART: - case ESP_SLEEP_WAKEUP_UNDEFINED: + case SW_RESET: // 0x03 Software reset digital core + case OWDT_RESET: // 0x04 Legacy watch dog reset digital core + case SDIO_RESET: // 0x06 Reset by SLC module, reset digital core + case TG0WDT_SYS_RESET: // 0x07 Timer Group0 Watch dog reset digital core + case TG1WDT_SYS_RESET: // 0x08 Timer Group1 Watch dog reset digital core + case RTCWDT_SYS_RESET: // 0x09 RTC Watch dog Reset digital core + case INTRUSION_RESET: // 0x0a Instrusion tested to reset CPU + case TGWDT_CPU_RESET: // 0x0b Time Group reset CPU + case RTCWDT_CPU_RESET: // 0x0d RTC Watch dog Reset CPU + case EXT_CPU_RESET: // 0x0e for APP CPU, reseted by PRO CPU + case RTCWDT_RTC_RESET: // 0x10 RTC Watch dog reset digital core and rtc mode default: - // not a deep sleep reset RTC_runmode = RUNMODE_POWERCYCLE; break; - } // switch + } ESP_LOGI(TAG, "Starting Software v%s, runmode %s", PROGVERSION, runmode[RTC_runmode]); @@ -72,6 +82,7 @@ void do_after_reset(void) { void enter_deepsleep(const uint64_t wakeup_sec = 60, gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { + // don't go to sleep while unjoined #if (HAS_LORA) if (!LMIC.devaddr) { ESP_LOGI(TAG, "Can't go to sleep while joining"); @@ -162,9 +173,11 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, dp_shutdown(); #endif -// reduce power if has PMU +// reduce power if has PMU or VEXT #ifdef HAS_PMU AXP192_power(pmu_power_sleep); +#elif EXT_POWER_SW + digitalWrite(EXT_POWER_SW, EXT_POWER_OFF); #endif // shutdown i2c bus From 0393fa3a456af51e08f7b97baef2cb93bc86d6d1 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 20:45:10 +0100 Subject: [PATCH 065/104] repair OTA & deepsleep combination --- src/cyclic.cpp | 13 +++---------- src/rcommand.cpp | 6 +++++- src/reset.cpp | 9 +++++++-- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/cyclic.cpp b/src/cyclic.cpp index a927a21e..88d618e5 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -20,16 +20,9 @@ void setCyclicIRQ() { // do all housekeeping void doHousekeeping() { - // check if update mode trigger switch was set - if (RTC_runmode == RUNMODE_UPDATE) { - // check battery status if we can before doing ota - if (batt_sufficient()) { - do_reset(true); // warmstart to runmode update - } else { - ESP_LOGE(TAG, "Battery level %d%% is too low for OTA", batt_level); - RTC_runmode = RUNMODE_NORMAL; // keep running in normal mode - } - } + // check if update mode trigger switch was set by rcommand + if (RTC_runmode == RUNMODE_UPDATE) + do_reset(true); // heap and task storage debugging ESP_LOGD(TAG, "Heap: Free:%d, Min:%d, Size:%d, Alloc:%d, StackHWM:%d", diff --git a/src/rcommand.cpp b/src/rcommand.cpp index d69bb0cc..9aad91ff 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -35,7 +35,11 @@ void set_reset(uint8_t val[]) { case 9: // reset and ask for software update via Wifi OTA ESP_LOGI(TAG, "Remote command: software update via Wifi"); #if (USE_OTA) - RTC_runmode = RUNMODE_UPDATE; + // check power status before scheduling ota update + if (batt_sufficient()) + RTC_runmode = RUNMODE_UPDATE; + else + ESP_LOGE(TAG, "Battery level %d%% is too low for OTA", batt_level); #endif // USE_OTA break; diff --git a/src/reset.cpp b/src/reset.cpp index 89720993..998367b1 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -49,7 +49,6 @@ void do_after_reset(void) { break; case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core - RTC_runmode = RUNMODE_WAKEUP; // calculate time spent in deep sleep gettimeofday(&sleep_stop_time, NULL); sleep_time_ms = @@ -57,6 +56,9 @@ void do_after_reset(void) { (sleep_stop_time.tv_usec - RTC_sleep_start_time.tv_usec) / 1000; ESP_LOGI(TAG, "Time spent in deep sleep: %d ms", sleep_time_ms); RTC_millis += sleep_time_ms; // increment system monotonic time + // set wakeup state, not if we have pending OTA update + if (RTC_runmode == RUNMODE_SLEEP) + RTC_runmode = RUNMODE_WAKEUP; break; case SW_RESET: // 0x03 Software reset digital core @@ -103,7 +105,7 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, // stop further enqueuing of senddata and MAC processing sendTimer.detach(); - // switch off radio + // switch off radio and other power consuming hardware #if (WIFICOUNTER) switch_wifi_sniffer(0); #endif @@ -111,6 +113,9 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, stop_BLEscan(); btStop(); #endif +#if (HAS_SDS011) + sds011_sleep(void); +#endif // stop MAC processing vTaskDelete(macProcessTask); From f74de6a27aa26035ee5dddd20d0bb1e2dd615520 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 20:53:04 +0100 Subject: [PATCH 066/104] move mqttloop from core0 to core1 --- src/main.cpp | 2 +- src/mqttclient.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index b52ce977..b5f69647 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -29,11 +29,11 @@ Task Core Prio Purpose ------------------------------------------------------------------------------- ledloop 0 3 blinks LEDs spiloop 0 2 reads/writes data on spi interface -mqttloop 0 2 reads/writes data on ETH interface IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer lmictask 1 2 MCCI LMiC LORAWAN stack clockloop 1 4 generates realtime telegrams for external clock +mqttloop 1 2 reads/writes data on ETH interface timesync_proc 1 3 processes realtime time sync requests irqhandler 1 2 cyclic tasks (i.e. displayrefresh) triggered by timers gpsloop 1 1 reads data from GPS via serial or i2c diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 867f06db..f66456ec 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -37,7 +37,7 @@ esp_err_t mqtt_init(void) { SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE); ESP_LOGI(TAG, "Starting MQTTloop..."); - xTaskCreate(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1, &mqttTask); + xTaskCreatePinnedToCore(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1, &mqttTask, 1); return ESP_OK; } From 9dc8e43f905aa370b956f62d70520057888aa032 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sat, 26 Dec 2020 23:36:56 +0100 Subject: [PATCH 067/104] mqttclient.cpp code sanitizations --- src/mqttclient.cpp | 55 ++++++---------------------------------------- 1 file changed, 7 insertions(+), 48 deletions(-) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index f66456ec..8e1d5aec 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -19,11 +19,8 @@ void mqtt_deinit(void) { esp_err_t mqtt_init(void) { - // setup network connection - WiFi.onEvent(NetworkEvent); + // setup network connection and MQTT client ETH.begin(); - - // setup mqtt client mqttClient.begin(MQTT_SERVER, MQTT_PORT, netClient); mqttClient.onMessageAdvanced(mqtt_callback); @@ -37,16 +34,14 @@ esp_err_t mqtt_init(void) { SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE); ESP_LOGI(TAG, "Starting MQTTloop..."); - xTaskCreatePinnedToCore(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1, &mqttTask, 1); - + xTaskCreatePinnedToCore(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1, + &mqttTask, 1); return ESP_OK; } int mqtt_connect(const char *my_host, const uint16_t my_port) { IPAddress mqtt_server_ip; - static String clientId = "paxcounter-" + ETH.macAddress(); - ESP_LOGI(TAG, "MQTT name is %s", MQTT_CLIENTNAME); // resolve server host name @@ -75,41 +70,6 @@ int mqtt_connect(const char *my_host, const uint16_t my_port) { return 0; } -void NetworkEvent(WiFiEvent_t event) { - switch (event) { - case SYSTEM_EVENT_ETH_START: - case SYSTEM_EVENT_STA_START: - ESP_LOGI(TAG, "Network link layer started"); - // ETH.setHostname(ETH.macAddress().c_str()); - break; - case SYSTEM_EVENT_ETH_STOP: - case SYSTEM_EVENT_STA_STOP: - ESP_LOGI(TAG, "Network link layer stopped"); - break; - case SYSTEM_EVENT_ETH_CONNECTED: - case SYSTEM_EVENT_STA_CONNECTED: - ESP_LOGI(TAG, "Network link connected"); - break; - case SYSTEM_EVENT_ETH_DISCONNECTED: - case SYSTEM_EVENT_STA_DISCONNECTED: - ESP_LOGI(TAG, "Network link disconnected"); - break; - case SYSTEM_EVENT_ETH_GOT_IP: - ESP_LOGI(TAG, "IP: %s", ETH.localIP().toString().c_str()); - ESP_LOGI(TAG, "Link Speed: %d Mbps %s", ETH.linkSpeed(), - ETH.fullDuplex() ? "full duplex" : "half duplex"); - mqtt_connect(MQTT_SERVER, MQTT_PORT); - break; - case SYSTEM_EVENT_STA_GOT_IP: - ESP_LOGI(TAG, "IP: %s", WiFi.localIP().toString().c_str()); - mqtt_connect(MQTT_SERVER, MQTT_PORT); - break; - - default: - break; - } -} - void mqtt_client_task(void *param) { MessageBuffer_t msg; @@ -128,21 +88,19 @@ void mqtt_client_task(void *param) { MQTT_KEEPALIVE * 1000 / portTICK_PERIOD_MS) != pdTRUE) continue; - // send data to mqtt server + // prepare data to send char buffer[PAYLOAD_BUFFER_SIZE + 3]; snprintf(buffer, msg.MessageSize + 3, "%u/%s", msg.MessagePort, msg.Message); + // send data to mqtt server and delete sent item from queue if (mqttClient.publish(MQTT_OUTTOPIC, buffer)) { ESP_LOGI(TAG, "%d byte(s) sent to MQTT server", msg.MessageSize + 2); - // delete sent item from queue xQueueReceive(MQTTSendQueue, &msg, (TickType_t)0); } else ESP_LOGD(TAG, "Couldn't sent message to MQTT server"); } else { // attempt to reconnect to MQTT server - ESP_LOGD(TAG, "MQTT error = %d / rc = %d", mqttClient.lastError(), - mqttClient.returnCode()); ESP_LOGD(TAG, "MQTT client reconnecting..."); delay(MQTT_RETRYSEC * 1000); mqtt_connect(MQTT_SERVER, MQTT_PORT); @@ -150,12 +108,13 @@ void mqtt_client_task(void *param) { } // while (1) } +// enqueue outgoing messages in MQTT send queue void mqtt_enqueuedata(MessageBuffer_t *message) { - // enqueue message in MQTT send queue if (xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0) != pdTRUE) ESP_LOGW(TAG, "MQTT sendqueue is full"); } +// process incoming MQTT messages void mqtt_callback(MQTTClient *client, char topic[], char payload[], int length) { if (strcmp(topic, MQTT_INTOPIC) == 0) From a4daa772c3186cdf9ba0283d937f49f48b11646a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 27 Dec 2020 00:36:23 +0100 Subject: [PATCH 068/104] generalize hash() function --- include/hash.h | 2 +- src/hash.cpp | 4 ++++ src/macsniff.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/include/hash.h b/include/hash.h index b86e7b2e..2caa6133 100644 --- a/include/hash.h +++ b/include/hash.h @@ -4,6 +4,6 @@ #include #include -uint32_t IRAM_ATTR rokkit(const char *data, int len); +uint32_t IRAM_ATTR hash(const char *data, int len); #endif \ No newline at end of file diff --git a/src/hash.cpp b/src/hash.cpp index e5d268f9..137e98a5 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -85,3 +85,7 @@ uint32_t IRAM_ATTR rokkit(const char *data, int len) { return hash; } + +uint32_t IRAM_ATTR hash(const char *data, int len) { + return rokkit(data, len); +} diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 6fefc07c..3e5e8210 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -141,7 +141,7 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { snprintf(buff, sizeof(buff), "%08X", *mac + (uint32_t)salt); // convert unsigned 32-bit salted MAC // to 8 digit hex string - uint16_t hashedmac = rokkit(&buff[3], 5); // hash MAC 8 digit -> 5 digit + uint16_t hashedmac = hash(&buff[3], 5); // hash MAC 8 digit -> 5 digit auto newmac = macs.insert(hashedmac); // add hashed MAC, if new unique bool added = newmac.second ? true : false; // true if hashed MAC is unique in container From 1a2f9e04394c926b217767b9867e9193b7ebb887 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 27 Dec 2020 00:40:07 +0100 Subject: [PATCH 069/104] mqttclient.cpp: hash mqtt client name --- include/mqttclient.h | 2 +- src/mqttclient.cpp | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/mqttclient.h b/include/mqttclient.h index 6e351e2a..5e595cca 100644 --- a/include/mqttclient.h +++ b/include/mqttclient.h @@ -17,7 +17,7 @@ #define MQTT_KEEPALIVE 10 // keep alive interval in seconds #ifndef MQTT_CLIENTNAME -#define MQTT_CLIENTNAME clientId.c_str() +#define MQTT_CLIENTNAME clientId #endif extern TaskHandle_t mqttTask; diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 8e1d5aec..6dd5a89a 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -41,7 +41,10 @@ esp_err_t mqtt_init(void) { int mqtt_connect(const char *my_host, const uint16_t my_port) { IPAddress mqtt_server_ip; - static String clientId = "paxcounter-" + ETH.macAddress(); + const uint16_t hashed = hash(Ð.macAddress()[0], 4); // hash MAC to 4 digits + char clientId[16]; + snprintf(clientId, 16, "paxcounter_%d", hashed); + ESP_LOGI(TAG, "MQTT name is %s", MQTT_CLIENTNAME); // resolve server host name From 310495e61c5bb51a073d0086db919220aa325719 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 27 Dec 2020 13:09:10 +0100 Subject: [PATCH 070/104] mqttclient.cpp bugfix hash MAC --- src/mqttclient.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 6dd5a89a..36da3c1c 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -41,9 +41,13 @@ esp_err_t mqtt_init(void) { int mqtt_connect(const char *my_host, const uint16_t my_port) { IPAddress mqtt_server_ip; - const uint16_t hashed = hash(Ð.macAddress()[0], 4); // hash MAC to 4 digits + uint8_t mac[6]; char clientId[16]; - snprintf(clientId, 16, "paxcounter_%d", hashed); + + // hash 6 digit MAC to 4 digits + esp_eth_get_mac(mac); + const uint16_t hashedmac = hash((const char *)mac, 4); + snprintf(clientId, 16, "paxcounter_%04x", hashedmac); ESP_LOGI(TAG, "MQTT name is %s", MQTT_CLIENTNAME); From e48fc5ad3fb7494df08a08fa1ef78566d658bb74 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 27 Dec 2020 13:12:46 +0100 Subject: [PATCH 071/104] hash.cpp: merge PR#3 in rokkithash code --- src/hash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hash.cpp b/src/hash.cpp index 137e98a5..5698bb9a 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -61,7 +61,7 @@ uint32_t IRAM_ATTR rokkit(const char *data, int len) { case 3: hash += *((uint16_t *)data); hash ^= hash << 16; - hash ^= ((signed char)data[2]) << 18; + hash ^= ((unit32_t)data[2]) << 18; hash += hash >> 11; break; case 2: From dd1ec2deedaf8691f844791eb0f6624bc075a25a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 27 Dec 2020 13:17:06 +0100 Subject: [PATCH 072/104] hash.cpp: fix typo --- src/hash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hash.cpp b/src/hash.cpp index 5698bb9a..172dca5d 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -61,7 +61,7 @@ uint32_t IRAM_ATTR rokkit(const char *data, int len) { case 3: hash += *((uint16_t *)data); hash ^= hash << 16; - hash ^= ((unit32_t)data[2]) << 18; + hash ^= ((uint32_t)data[2]) << 18; hash += hash >> 11; break; case 2: From 73263c3d044c1d7bd8e2a74a302817fd2c05b405 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 27 Dec 2020 23:43:45 +0100 Subject: [PATCH 073/104] externalize hash code to library --- include/hash.h | 2 +- platformio_orig.ini | 1 + src/hash.cpp | 52 +++------------------------------------------ 3 files changed, 5 insertions(+), 50 deletions(-) diff --git a/include/hash.h b/include/hash.h index 2caa6133..cfd96ce7 100644 --- a/include/hash.h +++ b/include/hash.h @@ -2,7 +2,7 @@ #define _HASH_H #include -#include +#include uint32_t IRAM_ATTR hash(const char *data, int len); diff --git a/platformio_orig.ini b/platformio_orig.ini index 3b013c97..27e0e688 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -79,6 +79,7 @@ lib_deps_sensors = boschsensortec/BSEC Software Library @ 1.6.1480 https://github.com/ricki-z/SDS011.git lib_deps_basic = + https://github.com/SukkoPera/Arduino-Rokkit-Hash.git bblanchon/ArduinoJson @ ^6 jchristensen/Timezone @ ^1.2.4 makuna/RTC @ ^2.3.5 diff --git a/src/hash.cpp b/src/hash.cpp index 172dca5d..32e8b1c5 100644 --- a/src/hash.cpp +++ b/src/hash.cpp @@ -36,55 +36,9 @@ #include "hash.h" -uint32_t IRAM_ATTR rokkit(const char *data, int len) { - uint32_t hash, tmp; - int rem; - - if (len <= 0 || data == 0) - return 0; - hash = len; - rem = len & 3; - len >>= 2; - - /* Main loop */ - while (len > 0) { - hash += *((uint16_t *)data); - tmp = (*((uint16_t *)(data + 2)) << 11) ^ hash; - hash = (hash << 16) ^ tmp; - data += 2 * 2; - hash += hash >> 11; - len--; - } - - /* Handle end cases */ - switch (rem) { - case 3: - hash += *((uint16_t *)data); - hash ^= hash << 16; - hash ^= ((uint32_t)data[2]) << 18; - hash += hash >> 11; - break; - case 2: - hash += *((uint16_t *)data); - hash ^= hash << 11; - hash += hash >> 17; - break; - case 1: - hash += (signed char)*data; - hash ^= hash << 10; - hash += hash >> 1; - } - - /* Force "avalanching" of final 127 bits */ - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - - return hash; -} +#ifdef ROKKIT_ENABLE_8BIT_OPTIMIZATIONS +#undef ROKKIT_ENABLE_8BIT_OPTIMIZATIONS +#endif uint32_t IRAM_ATTR hash(const char *data, int len) { return rokkit(data, len); From 70150fa606f914d852d0adbc8d08ae05f98ceb7f Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 28 Dec 2020 00:03:37 +0100 Subject: [PATCH 074/104] repair convert mac to mqtt clientname --- src/mqttclient.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 36da3c1c..6821a629 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -42,12 +42,12 @@ esp_err_t mqtt_init(void) { int mqtt_connect(const char *my_host, const uint16_t my_port) { IPAddress mqtt_server_ip; uint8_t mac[6]; - char clientId[16]; + char clientId[20]; - // hash 6 digit MAC to 4 digits + // hash 6 byte MAC to 4 byte hash esp_eth_get_mac(mac); - const uint16_t hashedmac = hash((const char *)mac, 4); - snprintf(clientId, 16, "paxcounter_%04x", hashedmac); + const uint32_t hashedmac = hash((const char *)mac, 6); + snprintf(clientId, 20, "paxcounter_%08x", hashedmac); ESP_LOGI(TAG, "MQTT name is %s", MQTT_CLIENTNAME); From 3cbaf240d9f4f43c497d0ecf9746da76e34587d4 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 28 Dec 2020 16:12:00 +0100 Subject: [PATCH 075/104] hash&salt code simplified --- include/cyclic.h | 1 + include/macsniff.h | 2 +- src/cyclic.cpp | 3 +-- src/macsniff.cpp | 34 +++++++++++++++++++--------------- src/main.cpp | 2 +- src/rcommand.cpp | 2 -- src/senddata.cpp | 1 - 7 files changed, 23 insertions(+), 22 deletions(-) diff --git a/include/cyclic.h b/include/cyclic.h index 8c7319be..d40891af 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -10,6 +10,7 @@ #include "display.h" #include "sds011read.h" #include "sdcard.h" +#include "macsniff.h" extern Ticker cyclicTimer; diff --git a/include/macsniff.h b/include/macsniff.h index 175cba58..6bc346ef 100644 --- a/include/macsniff.h +++ b/include/macsniff.h @@ -14,7 +14,7 @@ #include "corona.h" #endif -uint16_t get_salt(void); +uint32_t renew_salt(void); uint64_t macConvert(uint8_t *paddr); esp_err_t macQueueInit(void); void mac_process(void *pvParameters); diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 88d618e5..82a88a24 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -97,7 +97,6 @@ void doHousekeeping() { "free heap = %d bytes)", ESP.getMinFreeHeap(), ESP.getFreeHeap()); reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes if (ESP.getMinFreeHeap() <= MEM_LOW) // check again do_reset(true); // memory leak, reset device @@ -108,7 +107,6 @@ void doHousekeeping() { if (ESP.getMinFreePsram() <= MEM_LOW) { ESP_LOGI(TAG, "PSRAM full, counter cleared"); reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes if (ESP.getMinFreePsram() <= MEM_LOW) // check again do_reset(true); // memory leak, reset device @@ -140,6 +138,7 @@ void reset_counters() { macs.clear(); // clear all macs container macs_wifi = 0; macs_ble = 0; + renew_salt(); // get new salt #ifdef HAS_DISPLAY dp_plotCurve(0, true); #endif diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 3e5e8210..d47df342 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -9,10 +9,11 @@ static const char TAG[] = __FILE__; QueueHandle_t MacQueue; TaskHandle_t macProcessTask; -uint16_t salt = 0; +static uint32_t salt = renew_salt(); -uint16_t get_salt(void) { - salt = (uint16_t)random(65536); // get new 16bit random for salting hashes +uint32_t renew_salt(void) { + salt = esp_random(); + ESP_LOGV(TAG, "new salt = %04X", salt); return salt; } @@ -101,9 +102,6 @@ void IRAM_ATTR mac_add(uint8_t *paddr, int8_t rssi, snifftype_t sniff_type) { uint16_t mac_analyze(MacBuffer_t MacBuffer) { - if (salt == 0) // ensure we have salt (appears after radio is turned on) - return 0; - if ((cfg.rssilimit) && (MacBuffer.rssi < cfg.rssilimit)) { // rssi is negative value ESP_LOGI(TAG, "%s RSSI %d -> ignoring (limit: %d)", @@ -126,8 +124,7 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { } }; - char buff[10]; // temporary buffer for printf - uint32_t *mac; // temporary buffer for shortened MAC + uint32_t *mac; // pointer to shortened 4 byte MAC // only last 3 MAC Address bytes are used for MAC address anonymization // but since it's uint32 we take 4 bytes to avoid 1st value to be 0. @@ -138,11 +135,15 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { // and increment counter on display // https://en.wikipedia.org/wiki/MAC_Address_Anonymization - snprintf(buff, sizeof(buff), "%08X", - *mac + (uint32_t)salt); // convert unsigned 32-bit salted MAC - // to 8 digit hex string - uint16_t hashedmac = hash(&buff[3], 5); // hash MAC 8 digit -> 5 digit - auto newmac = macs.insert(hashedmac); // add hashed MAC, if new unique + // reversed 4 byte MAC added to current salt + const uint32_t saltedmac = *mac + salt; + + // hashed 4 byte MAC + // to save RAM, we use only lower 2 bytes of hash, since collisions don't + // matter in our use case + const uint16_t hashedmac = hash((const char *)&saltedmac, 4); + + auto newmac = macs.insert(hashedmac); // add hashed MAC, if new unique bool added = newmac.second ? true : false; // true if hashed MAC is unique in container @@ -183,7 +184,8 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { // Log scan result ESP_LOGV(TAG, - "%s %s RSSI %ddBi -> salted MAC %s -> Hash %04X -> WiFi:%d " + "%s %s RSSI %ddBi -> MAC %0x:%0x:%0x:%0x:%0x:%0x -> salted %04X" + " -> hashed %04X -> WiFi:%d " "BLTH:%d " #if (COUNT_ENS) "(CWA:%d)" @@ -191,7 +193,9 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { "-> %d Bytes left", added ? "new " : "known", MacBuffer.sniff_type == MAC_SNIFF_WIFI ? "WiFi" : "BLTH", - MacBuffer.rssi, buff, hashedmac, macs_wifi, macs_ble, + MacBuffer.rssi, MacBuffer.mac[0], MacBuffer.mac[1], MacBuffer.mac[2], + MacBuffer.mac[3], MacBuffer.mac[4], MacBuffer.mac[5], saltedmac, + hashedmac, macs_wifi, macs_ble, #if (COUNT_ENS) cwa_report(), #endif diff --git a/src/main.cpp b/src/main.cpp index b5f69647..5d7bb62b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -428,7 +428,7 @@ void setup() { // initialize salt value using esp_random() called by random() in // arduino-esp32 core. Note: do this *after* wifi has started, since // function gets it's seed from RF noise - get_salt(); // get new 16bit for salting hashes + reset_counters(); // start state machine ESP_LOGI(TAG, "Starting Interrupt Handler..."); diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 9aad91ff..23116014 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -18,7 +18,6 @@ void set_reset(uint8_t val[]) { case 1: // reset MAC counter ESP_LOGI(TAG, "Remote command: reset MAC counter"); reset_counters(); // clear macs - get_salt(); // get new salt break; case 2: // reset device to factory settings ESP_LOGI(TAG, "Remote command: reset device to factory settings"); @@ -119,7 +118,6 @@ void set_countmode(uint8_t val[]) { return; } reset_counters(); // clear macs - get_salt(); // get new salt } void set_screensaver(uint8_t val[]) { diff --git a/src/senddata.cpp b/src/senddata.cpp index 6c10be82..df9c2411 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -115,7 +115,6 @@ void sendData() { // clear counter if not in cumulative counter mode if (cfg.countermode != 1) { reset_counters(); // clear macs container and reset all counters - get_salt(); // get new salt for salting hashes ESP_LOGI(TAG, "Counter cleared"); } #ifdef HAS_DISPLAY From 19045654e7e980782dcd3dd423d96615142b2db5 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 28 Dec 2020 16:41:07 +0100 Subject: [PATCH 076/104] move mqtt settings to paxcounter_orig.conf --- include/mqttclient.h | 10 ---------- src/paxcounter_orig.conf | 12 ++++++++++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/include/mqttclient.h b/include/mqttclient.h index 5e595cca..a4fe4ac1 100644 --- a/include/mqttclient.h +++ b/include/mqttclient.h @@ -6,16 +6,6 @@ #include #include -#define MQTT_ETHERNET 0 // select PHY: set 0 for Wifi, 1 for ethernet -#define MQTT_INTOPIC "paxin" -#define MQTT_OUTTOPIC "paxout" -#define MQTT_PORT 1883 -#define MQTT_SERVER "paxcounter.cloud.shiftr.io" -#define MQTT_USER "public" -#define MQTT_PASSWD "public" -#define MQTT_RETRYSEC 20 // retry reconnect every 20 seconds -#define MQTT_KEEPALIVE 10 // keep alive interval in seconds - #ifndef MQTT_CLIENTNAME #define MQTT_CLIENTNAME clientId #endif diff --git a/src/paxcounter_orig.conf b/src/paxcounter_orig.conf index 4c1a7225..14481f22 100644 --- a/src/paxcounter_orig.conf +++ b/src/paxcounter_orig.conf @@ -121,3 +121,15 @@ #define CAYENNE_DEVICECONFIG 11 // device period configuration #define CAYENNE_SENSORREAD 13 // sensor period configuration #define CAYENNE_SENSORENABLE 14 // sensor enable configuration + +// MQTT settings, only needed if MQTT is used (#define HAS_MQTT in board hal file) +#define MQTT_ETHERNET 0 // select PHY: set 0 for Wifi, 1 for ethernet +#define MQTT_INTOPIC "paxin" +#define MQTT_OUTTOPIC "paxout" +#define MQTT_PORT 1883 +#define MQTT_SERVER "public.cloud.shiftr.io" +#define MQTT_USER "public" +#define MQTT_PASSWD "public" +#define MQTT_RETRYSEC 20 // retry reconnect every 20 seconds +#define MQTT_KEEPALIVE 10 // keep alive interval in seconds +//#define MQTT_CLIENTNAME "my_paxcounter" // generated by default From a3249203d0e5506b69b39be4b8ac63a6da85f5c0 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 28 Dec 2020 18:31:31 +0100 Subject: [PATCH 077/104] handle join during sleep --- src/lorawan.cpp | 10 +++------- src/reset.cpp | 35 ++++++++--------------------------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/lorawan.cpp b/src/lorawan.cpp index aa0e9ccd..440b4a47 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -284,14 +284,10 @@ esp_err_t lmic_init(void) { // Pass OTA parameters to LMIC_setSession #else // load saved session from RTC, if we have one - if (RTC_runmode == RUNMODE_WAKEUP) { + if (RTC_runmode == RUNMODE_WAKEUP) LoadLMICFromRTC(); - } - // otherwise start join procedure if not already joined - else { - if (!LMIC_startJoining()) - ESP_LOGI(TAG, "Already joined"); - } + if (!LMIC_startJoining()) + ESP_LOGI(TAG, "Already joined"); #endif // start lmic loop task diff --git a/src/reset.cpp b/src/reset.cpp index 998367b1..e5961d75 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -84,24 +84,15 @@ void do_after_reset(void) { void enter_deepsleep(const uint64_t wakeup_sec = 60, gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { - // don't go to sleep while unjoined -#if (HAS_LORA) - if (!LMIC.devaddr) { - ESP_LOGI(TAG, "Can't go to sleep while joining"); - return; - } -#endif + ESP_LOGI(TAG, "Preparing to sleep..."); + RTC_runmode = RUNMODE_SLEEP; int i; // validate wake up pin, if we have if (!GPIO_IS_VALID_GPIO(wakeup_gpio)) wakeup_gpio = GPIO_NUM_MAX; - ESP_LOGI(TAG, "Preparing to sleep..."); - - RTC_runmode = RUNMODE_SLEEP; - // stop further enqueuing of senddata and MAC processing sendTimer.detach(); @@ -125,27 +116,23 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, // wait a while (max 100 sec) to clear send queues ESP_LOGI(TAG, "Waiting until send queues are empty..."); - for (i = 10; i > 0; i--) { + for (i = 100; i > 0; i--) { if (!allQueuesEmtpy()) - vTaskDelay(pdMS_TO_TICKS(10000)); + vTaskDelay(pdMS_TO_TICKS(1000)); else break; } - if (i == 0) - goto Error; - // shutdown LMIC safely, waiting max 100 sec + // shutdown LMIC safely, waiting max 100 sec #if (HAS_LORA) ESP_LOGI(TAG, "Waiting until LMIC is idle..."); - for (i = 10; i > 0; i--) { + for (i = 100; i > 0; i--) { if ((LMIC.opmode & OP_TXRXPEND) || os_queryTimeCriticalJobs(sec2osticks(wakeup_sec))) - vTaskDelay(pdMS_TO_TICKS(10000)); + vTaskDelay(pdMS_TO_TICKS(1000)); else break; } - if (i == 0) - goto Error; #endif // (HAS_LORA) // shutdown MQTT safely @@ -165,10 +152,8 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, else break; } - if (i == 0) - goto Error; - // save LMIC state to RTC RAM + // save LMIC state to RTC RAM #if (HAS_LORA) SaveLMICToRTC(wakeup_sec); #endif // (HAS_LORA) @@ -207,10 +192,6 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, RTC_millis += millis(); ESP_LOGI(TAG, "Going to sleep, good bye."); esp_deep_sleep_start(); - -Error: - ESP_LOGE(TAG, "Can't go to sleep. Resetting."); - do_reset(true); } unsigned long long uptime() { return (RTC_millis + millis()); } \ No newline at end of file From a0f3d8929550aa66764beb7cdfb0edefc624dd9a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Mon, 28 Dec 2020 18:32:47 +0100 Subject: [PATCH 078/104] sanitize BME sensor detection logic --- src/bmesensor.cpp | 65 ++++++++--------------------------------------- src/main.cpp | 4 ++- 2 files changed, 14 insertions(+), 55 deletions(-) diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 9ba31fcf..6baa0f1c 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -46,10 +46,10 @@ Adafruit_BMP085 bmp; // I2C void setBMEIRQ() { xTaskNotify(irqHandlerTask, BME_IRQ, eSetBits); } // initialize MEMS sensor +// return = 0 -> error / return = 1 -> success int bme_init(void) { - // return = 0 -> error / return = 1 -> success - int rc = 1; + int rc = 0; #ifdef HAS_BME680 // block i2c bus access @@ -63,76 +63,33 @@ int bme_init(void) { iaqSensor.version.minor_bugfix); iaqSensor.setConfig(bsec_config_iaq); - - if (checkIaqSensorStatus()) - ESP_LOGI(TAG, "BME680 sensor found and initialized"); - else { - ESP_LOGE(TAG, "BME680 sensor not found"); - rc = 0; - goto finish; - } - loadState(); - iaqSensor.setTemperatureOffset((float)BME_TEMP_OFFSET); iaqSensor.updateSubscription(sensorList, 10, BSEC_SAMPLE_RATE_LP); - if (checkIaqSensorStatus()) - ESP_LOGI(TAG, "BSEC subscription succesful"); - else { - ESP_LOGE(TAG, "BSEC subscription error"); - rc = 0; - goto finish; - } - } else { + rc = checkIaqSensorStatus(); + + } else ESP_LOGE(TAG, "I2c bus busy - BME680 initialization error"); - rc = 0; - goto finish; - } #elif defined HAS_BME280 - - bool status; - - // block i2c bus access if (I2C_MUTEX_LOCK()) { - - status = bme.begin(BME280_ADDR); - if (!status) { - ESP_LOGE(TAG, "BME280 sensor not found"); - rc = 0; - goto finish; - } - ESP_LOGI(TAG, "BME280 sensor found and initialized"); - } else { + rc = bme.begin(BME280_ADDR); + } else ESP_LOGE(TAG, "I2c bus busy - BME280 initialization error"); - rc = 0; - goto finish; - } #elif defined HAS_BMP180 - bool status; - // block i2c bus access if (I2C_MUTEX_LOCK()) { // Wire.begin(21, 22); - status = bmp.begin(); - if (!status) { - ESP_LOGE(TAG, "BMP180 sensor not found"); - rc = 0; - goto finish; - } - ESP_LOGI(TAG, "BMP180 sensor found and initialized"); - } else { + rc = bmp.begin(); + } else ESP_LOGE(TAG, "I2c bus busy - BMP180 initialization error"); - rc = 0; - goto finish; - } + #endif -finish: I2C_MUTEX_UNLOCK(); // release i2c bus access if (rc) - bmecycler.attach(BMECYCLE, setBMEIRQ); + bmecycler.attach(BMECYCLE, setBMEIRQ); // start cyclic data transmit return rc; } // bme_init() diff --git a/src/main.cpp b/src/main.cpp index 5d7bb62b..33fe1ae5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -451,8 +451,10 @@ void setup() { #endif if (bme_init()) ESP_LOGI(TAG, "BME sensor initialized"); - else + else { ESP_LOGE(TAG, "BME sensor could not be initialized"); + cfg.payloadmask &= ~MEMS_DATA; // switch off transmit of BME data + } #endif // starting timers and interrupts From d23bc3535ed93b8d7a8242cdfc4b82dccf508f78 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 29 Dec 2020 00:21:40 +0100 Subject: [PATCH 079/104] enter_deepsleep cleanup wait logic --- src/reset.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/reset.cpp b/src/reset.cpp index e5961d75..8332589d 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -117,10 +117,9 @@ void enter_deepsleep(const uint64_t wakeup_sec = 60, // wait a while (max 100 sec) to clear send queues ESP_LOGI(TAG, "Waiting until send queues are empty..."); for (i = 100; i > 0; i--) { - if (!allQueuesEmtpy()) - vTaskDelay(pdMS_TO_TICKS(1000)); - else + if (allQueuesEmtpy()) break; + vTaskDelay(pdMS_TO_TICKS(1000)); } // shutdown LMIC safely, waiting max 100 sec From df8083dba67838959d9f02368e10d235b56fc93b Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 29 Dec 2020 14:25:59 +0100 Subject: [PATCH 080/104] switch off DIO IRQs (MCCI LMIC PR #556) --- src/lmic_config.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lmic_config.h b/src/lmic_config.h index 992e4a18..d5dcfdbe 100644 --- a/src/lmic_config.h +++ b/src/lmic_config.h @@ -18,9 +18,10 @@ // use interrupts only if LORA_IRQ and LORA_DIO are connected to interrupt // capable and separate GPIO pins on your board, if not don't enable -#if (LORA_IRQ) != (LORA_IO1) -#define LMIC_USE_INTERRUPTS 1 -#endif +// note: this feature can't be used on ESP32 unless PR #556 of MCCI LMIC was merged +//#if (LORA_IRQ) != (LORA_IO1) +//#define LMIC_USE_INTERRUPTS 1 +//#endif // avoid lmic warning if we don't configure radio in case we haven't one #if !(defined(CFG_sx1272_radio) || defined(CFG_sx1276_radio)) From 0209915877ae495f4245df1a9632ddce42f81dd2 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 29 Dec 2020 20:48:02 +0100 Subject: [PATCH 081/104] macsniff.cpp code sanitization --- src/macsniff.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/macsniff.cpp b/src/macsniff.cpp index d47df342..e5044039 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -102,6 +102,10 @@ void IRAM_ATTR mac_add(uint8_t *paddr, int8_t rssi, snifftype_t sniff_type) { uint16_t mac_analyze(MacBuffer_t MacBuffer) { + uint32_t *mac; // pointer to shortened 4 byte MAC + uint32_t saltedmac; + uint16_t hashedmac; + if ((cfg.rssilimit) && (MacBuffer.rssi < cfg.rssilimit)) { // rssi is negative value ESP_LOGI(TAG, "%s RSSI %d -> ignoring (limit: %d)", @@ -124,8 +128,6 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { } }; - uint32_t *mac; // pointer to shortened 4 byte MAC - // only last 3 MAC Address bytes are used for MAC address anonymization // but since it's uint32 we take 4 bytes to avoid 1st value to be 0. // this gets MAC in msb (= reverse) order, but doesn't matter for hashing it. @@ -136,12 +138,12 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { // https://en.wikipedia.org/wiki/MAC_Address_Anonymization // reversed 4 byte MAC added to current salt - const uint32_t saltedmac = *mac + salt; + saltedmac = *mac + salt; // hashed 4 byte MAC // to save RAM, we use only lower 2 bytes of hash, since collisions don't // matter in our use case - const uint16_t hashedmac = hash((const char *)&saltedmac, 4); + hashedmac = hash((const char *)&saltedmac, 4); auto newmac = macs.insert(hashedmac); // add hashed MAC, if new unique bool added = From e882ad625e021f18ffa94cd861a03a22e0c3a27a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Tue, 29 Dec 2020 20:48:32 +0100 Subject: [PATCH 082/104] mqttclient.cpp: added base64 encoding --- src/mqttclient.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 6821a629..4e43244b 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -1,6 +1,7 @@ #ifdef HAS_MQTT #include "mqttclient.h" +#include static const char TAG[] = __FILE__; @@ -95,14 +96,15 @@ void mqtt_client_task(void *param) { MQTT_KEEPALIVE * 1000 / portTICK_PERIOD_MS) != pdTRUE) continue; - // prepare data to send - char buffer[PAYLOAD_BUFFER_SIZE + 3]; - snprintf(buffer, msg.MessageSize + 3, "%u/%s", msg.MessagePort, - msg.Message); + // prepare mqtt topic + char topic[16]; + snprintf(topic, 16, "%s/%u", MQTT_OUTTOPIC, msg.MessagePort); - // send data to mqtt server and delete sent item from queue - if (mqttClient.publish(MQTT_OUTTOPIC, buffer)) { - ESP_LOGI(TAG, "%d byte(s) sent to MQTT server", msg.MessageSize + 2); + // send base64 encoded message to mqtt server and delete it from queue + if (mqttClient.publish(topic, + base64::encode(msg.Message, msg.MessageSize))) { + ESP_LOGD(TAG, "%s/%s sent to MQTT server", topic, + base64::encode(msg.Message, msg.MessageSize)); xQueueReceive(MQTTSendQueue, &msg, (TickType_t)0); } else ESP_LOGD(TAG, "Couldn't sent message to MQTT server"); From bf5a376926ecc0091abb6f963b546c4496c89272 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 30 Dec 2020 22:07:50 +0100 Subject: [PATCH 083/104] fix issue #697 --- include/reset.h | 3 ++- src/reset.cpp | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/reset.h b/include/reset.h index af9d6c50..ddf3dfcb 100644 --- a/include/reset.h +++ b/include/reset.h @@ -11,7 +11,8 @@ void do_reset(bool warmstart); void do_after_reset(void); -void enter_deepsleep(const uint64_t wakeup_sec, const gpio_num_t wakeup_gpio); +void enter_deepsleep(const uint64_t wakeup_sec = 60, + const gpio_num_t wakeup_gpio = GPIO_NUM_MAX); unsigned long long uptime(void); #endif // _RESET_H \ No newline at end of file diff --git a/src/reset.cpp b/src/reset.cpp index 8332589d..3bd2a619 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -81,8 +81,7 @@ void do_after_reset(void) { runmode[RTC_runmode]); } -void enter_deepsleep(const uint64_t wakeup_sec = 60, - gpio_num_t wakeup_gpio = GPIO_NUM_MAX) { +void enter_deepsleep(const uint64_t wakeup_sec, gpio_num_t wakeup_gpio) { ESP_LOGI(TAG, "Preparing to sleep..."); From 5d8cec547cd2543cc13bb26949e19ee29423a205 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 30 Dec 2020 22:09:06 +0100 Subject: [PATCH 084/104] lopy4.h: remove LORA_RST, now in pins_arduino.h --- src/hal/lopy4.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hal/lopy4.h b/src/hal/lopy4.h index 8072dfd0..fd99a7d7 100644 --- a/src/hal/lopy4.h +++ b/src/hal/lopy4.h @@ -10,7 +10,6 @@ // Hardware related definitions for Pycom LoPy4 Board #define HAS_LORA 1 // comment out if device shall not send data via LoRa -#define LORA_RST LMIC_UNUSED_PIN // reset pin of lora chip is not wired von LoPy4 //#defin HAS_SPI 1 // comment out if device shall not send data via SPI // pin definitions for local wired SPI slave interface From 98f83d96794147b886a231bbc506d4b3eb4a9fe8 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 30 Dec 2020 22:25:37 +0100 Subject: [PATCH 085/104] MQTT client rcommand completion --- README.md | 4 +++- include/mqttclient.h | 4 ++-- src/mqttclient.cpp | 43 ++++++++++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 79634f3a..9dc8660d 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-L - Generic ESP32 Depending on board hardware following features are supported: +- LoRaWAN communication, supporting various payload formats (see enclosed .js converters) +- MQTT communication via TCP/IP and Ethernet interface (note: payload transmitted over MQTT will be base64 encoded) +- SPI serial communication to a local host - LED (shows power & status) - OLED Display (shows detailed status) - RGB LED (shows colorized status) @@ -62,7 +65,6 @@ Depending on board hardware following features are supported: - Switch external power / battery - LED Matrix display (similar to [this 64x16 model](https://www.instructables.com/id/64x16-RED-LED-Marquee/), can be ordered on [Aliexpress](https://www.aliexpress.com/item/P3-75-dot-matrix-led-module-3-75mm-high-clear-top1-for-text-display-304-60mm/32616683948.html)) - SD-card (see section SD-card here) for logging pax data -- Ethernet interface for MQTT communication via TCP/IP Target platform must be selected in `platformio.ini`.
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory. If you want to use a ESP32 board which is not yet supported, use hal file generic.h and tailor pin mappings to your needs. Pull requests for new boards welcome.
diff --git a/include/mqttclient.h b/include/mqttclient.h index a4fe4ac1..1c797a01 100644 --- a/include/mqttclient.h +++ b/include/mqttclient.h @@ -5,6 +5,7 @@ #include "rcommand.h" #include #include +#include #ifndef MQTT_CLIENTNAME #define MQTT_CLIENTNAME clientId @@ -17,8 +18,7 @@ uint32_t mqtt_queuewaiting(void); void mqtt_queuereset(void); void mqtt_client_task(void *param); int mqtt_connect(const char *my_host, const uint16_t my_port); -void mqtt_callback(MQTTClient *client, char topic[], char payload[], - int length); +void mqtt_callback(MQTTClient *client, char *topic, char *payload, int length); void NetworkEvent(WiFiEvent_t event); esp_err_t mqtt_init(void); void mqtt_deinit(void); diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 4e43244b..b890c920 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -1,7 +1,6 @@ #ifdef HAS_MQTT #include "mqttclient.h" -#include static const char TAG[] = __FILE__; @@ -99,12 +98,20 @@ void mqtt_client_task(void *param) { // prepare mqtt topic char topic[16]; snprintf(topic, 16, "%s/%u", MQTT_OUTTOPIC, msg.MessagePort); + size_t out_len = 0; - // send base64 encoded message to mqtt server and delete it from queue - if (mqttClient.publish(topic, - base64::encode(msg.Message, msg.MessageSize))) { - ESP_LOGD(TAG, "%s/%s sent to MQTT server", topic, - base64::encode(msg.Message, msg.MessageSize)); + // get length of base64 encoded message + mbedtls_base64_encode(NULL, 0, &out_len, (unsigned char *)msg.Message, + msg.MessageSize); + + // base64 encode the message + unsigned char encoded[out_len]; + mbedtls_base64_encode(encoded, out_len, &out_len, + (unsigned char *)msg.Message, msg.MessageSize); + + // send encoded message to mqtt server and delete it from queue + if (mqttClient.publish(topic, (const char *)encoded, out_len)) { + ESP_LOGD(TAG, "%u bytes sent to MQTT server", out_len); xQueueReceive(MQTTSendQueue, &msg, (TickType_t)0); } else ESP_LOGD(TAG, "Couldn't sent message to MQTT server"); @@ -117,19 +124,29 @@ void mqtt_client_task(void *param) { } // while (1) } +// process incoming MQTT messages +void mqtt_callback(MQTTClient *client, char *topic, char *payload, int length) { + if (strcmp(topic, MQTT_INTOPIC) == 0) { + + // get length of base64 encoded message + size_t out_len = 0; + mbedtls_base64_decode(NULL, 0, &out_len, (unsigned char *)payload, length); + + // decode the base64 message + unsigned char decoded[out_len]; + mbedtls_base64_decode(decoded, out_len, &out_len, (unsigned char *)payload, + length); + + rcommand(decoded, out_len); + } +} + // enqueue outgoing messages in MQTT send queue void mqtt_enqueuedata(MessageBuffer_t *message) { if (xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0) != pdTRUE) ESP_LOGW(TAG, "MQTT sendqueue is full"); } -// process incoming MQTT messages -void mqtt_callback(MQTTClient *client, char topic[], char payload[], - int length) { - if (strcmp(topic, MQTT_INTOPIC) == 0) - rcommand((const uint8_t *)payload, (const uint8_t)length); -} - void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); } uint32_t mqtt_queuewaiting(void) { From 4093a39e09dab214713bfbf38fcfd960a6e3d7cc Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Wed, 30 Dec 2020 22:28:35 +0100 Subject: [PATCH 086/104] bump to v2.1.1 --- platformio_orig.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio_orig.ini b/platformio_orig.ini index 27e0e688..a6cf1ac7 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -47,7 +47,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 2.1.0 +release_version = 2.1.1 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 From 2d2a8abd79b515437c4d959dea249f943f996651 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 31 Dec 2020 17:47:48 +0100 Subject: [PATCH 087/104] mqttclient.cpp: unsubcribe & keepalive --- src/mqttclient.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index b890c920..3be38ba9 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -12,6 +12,7 @@ WiFiClient netClient; MQTTClient mqttClient; void mqtt_deinit(void) { + mqttClient.unsubscribe(MQTT_INTOPIC); mqttClient.onMessageAdvanced(NULL); mqttClient.disconnect(); vTaskDelete(mqttTask); @@ -22,6 +23,7 @@ esp_err_t mqtt_init(void) { // setup network connection and MQTT client ETH.begin(); mqttClient.begin(MQTT_SERVER, MQTT_PORT, netClient); + mqttClient.setKeepAlive(MQTT_KEEPALIVE); mqttClient.onMessageAdvanced(mqtt_callback); _ASSERT(SEND_QUEUE_SIZE > 0); From 61c7e4c12f2faf7077eb5f9e9b252767025477f0 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 1 Jan 2021 15:25:56 +0100 Subject: [PATCH 088/104] process rcmd via queue --- README.md | 2 +- include/rcommand.h | 18 ++++++++++--- src/cyclic.cpp | 2 ++ src/main.cpp | 7 ++++- src/rcommand.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++---- src/reset.cpp | 10 +------ src/senddata.cpp | 6 ++--- 7 files changed, 90 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 9dc8660d..0f0c0529 100644 --- a/README.md +++ b/README.md @@ -364,7 +364,7 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering. # Remote control -The device listenes for remote control commands on LoRaWAN Port 2. Multiple commands per downlink are possible by concatenating them. +The device listenes for remote control commands on LoRaWAN Port 2. Multiple commands per downlink are possible by concatenating them, but must not exceed a maximum of 10 bytes per downlink. Note: all settings are stored in NVRAM and will be reloaded when device starts. diff --git a/include/rcommand.h b/include/rcommand.h index c26bce84..696a23d2 100644 --- a/include/rcommand.h +++ b/include/rcommand.h @@ -15,6 +15,11 @@ #include "timesync.h" #include "blescan.h" +// maximum number of elements in rcommand interpreter queue +#define RCMD_QUEUE_SIZE 5 + +extern TaskHandle_t rcmdTask; + // table of remote commands and assigned functions typedef struct { const uint8_t opcode; @@ -23,9 +28,16 @@ typedef struct { const bool store; } cmd_t; -extern bool rcmd_busy; +// Struct for remote command processing queue +typedef struct { + uint8_t cmd[10]; + uint8_t cmdLen; +} RcmdBuffer_t; -void rcommand(const uint8_t cmd[], const uint8_t cmdlength); -void do_reset(bool warmstart); +void IRAM_ATTR rcommand(const uint8_t *cmd, const size_t cmdlength); +void rcmd_queuereset(void); +uint32_t rcmd_queuewaiting(void); +void rcmd_deinit(void); +esp_err_t rcmd_init(void); #endif diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 82a88a24..ae26d4ce 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -34,6 +34,8 @@ void doHousekeeping() { ESP_LOGD(TAG, "MACprocessor %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(macProcessTask), eTaskGetState(macProcessTask)); + ESP_LOGD(TAG, "Rcommand interpreter %d bytes left | Taskstate = %d", + uxTaskGetStackHighWaterMark(rcmdTask), eTaskGetState(rcmdTask)); #if (HAS_LORA) ESP_LOGD(TAG, "LMiCtask %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(lmicTask), eTaskGetState(lmicTask)); diff --git a/src/main.cpp b/src/main.cpp index 33fe1ae5..6bc041b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,7 +38,8 @@ timesync_proc 1 3 processes realtime time sync requests irqhandler 1 2 cyclic tasks (i.e. displayrefresh) triggered by timers gpsloop 1 1 reads data from GPS via serial or i2c lorasendtask 1 1 feeds data from lora sendqueue to lmcic -macprocess 1 1 analyzes sniffed MACs +macprocess 1 1 MAC analyzer loop +rmcd_process 1 1 Remote command interpreter loop IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator Low priority numbers denote low priority tasks. @@ -291,6 +292,10 @@ void setup() { ESP_LOGI(TAG, "Starting MAC processor..."); macQueueInit(); + // start rcommand processing task + ESP_LOGI(TAG, "Starting rcommand interpreter..."); + rcmd_init(); + // start BLE scan callback if BLE function is enabled in NVRAM configuration // or remove bluetooth stack from RAM, if option bluetooth is not compiled #if (BLECOUNTER) diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 23116014..b568b505 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -5,8 +5,8 @@ // Local logging tag static const char TAG[] = __FILE__; -// global variable indicating if rcommand() is executing -bool rcmd_busy = false; +QueueHandle_t RcmdQueue; +TaskHandle_t rcmdTask; // set of functions that can be triggered by remote commands void set_reset(uint8_t val[]) { @@ -396,14 +396,13 @@ static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table // check and execute remote command -void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { +void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { if (cmdlength == 0) return; uint8_t foundcmd[cmdlength], cursor = 0; bool storeflag = false; - rcmd_busy = true; while (cursor < cmdlength) { @@ -436,6 +435,64 @@ void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { if (storeflag) saveConfig(); - rcmd_busy = false; +} // rcmd_execute() +// remote command processing task +void rcmd_process(void *pvParameters) { + _ASSERT((uint32_t)pvParameters == 1); // FreeRTOS check + + RcmdBuffer_t RcmdBuffer; + + while (1) { + // fetch next or wait for incoming rcommand from queue + if (xQueueReceive(RcmdQueue, &RcmdBuffer, portMAX_DELAY) != pdTRUE) { + ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); + continue; + } + rcmd_execute(RcmdBuffer.cmd, RcmdBuffer.cmdLen); + } + + delay(2); // yield to CPU +} // rcmd_process() + +// enqueue remote command +void IRAM_ATTR rcommand(const uint8_t *cmd, const size_t cmdlength) { + + RcmdBuffer_t rcmd = {0}; + + rcmd.cmdLen = cmdlength; + memcpy(rcmd.cmd, cmd, cmdlength); + + if (xQueueSendToBack(RcmdQueue, (void *)&rcmd, (TickType_t)0) != pdTRUE) + ESP_LOGW(TAG, "Remote command queue is full"); } // rcommand() + +void rcmd_queuereset(void) { xQueueReset(RcmdQueue); } + +uint32_t rcmd_queuewaiting(void) { return uxQueueMessagesWaiting(RcmdQueue); } + +void rcmd_deinit(void) { + rcmd_queuereset(); + vTaskDelete(rcmdTask); +} + +esp_err_t rcmd_init(void) { + + _ASSERT(RCMD_QUEUE_SIZE > 0); + RcmdQueue = xQueueCreate(RCMD_QUEUE_SIZE, sizeof(RcmdBuffer_t)); + if (RcmdQueue == 0) { + ESP_LOGE(TAG, "Could not create rcommand send queue. Aborting."); + return ESP_FAIL; + } + ESP_LOGI(TAG, "Rcommand send queue created, size %d Bytes", RCMD_QUEUE_SIZE); + + xTaskCreatePinnedToCore(rcmd_process, // task function + "rcmdloop", // name of task + 3072, // stack size of task + (void *)1, // parameter of the task + 1, // priority of the task + &rcmdTask, // task handle + 1); // CPU core + + return ESP_OK; +} // rcmd_init() \ No newline at end of file diff --git a/src/reset.cpp b/src/reset.cpp index 3bd2a619..7c0b0d48 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -143,15 +143,7 @@ void enter_deepsleep(const uint64_t wakeup_sec, gpio_num_t wakeup_gpio) { spi_deinit(); #endif - // wait until rcommands are all done - for (i = 10; i > 0; i--) { - if (rcmd_busy) - vTaskDelay(pdMS_TO_TICKS(1000)); - else - break; - } - - // save LMIC state to RTC RAM +// save LMIC state to RTC RAM #if (HAS_LORA) SaveLMICToRTC(wakeup_sec); #endif // (HAS_LORA) diff --git a/src/senddata.cpp b/src/senddata.cpp index df9c2411..81aa2004 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -12,8 +12,7 @@ void SendPayload(uint8_t port) { ESP_LOGD(TAG, "sending Payload for Port %d", port); - MessageBuffer_t - SendBuffer; // contains MessageSize, MessagePort, Message[] + MessageBuffer_t SendBuffer; // contains MessageSize, MessagePort, Message[] SendBuffer.MessageSize = payload.getSize(); @@ -190,6 +189,7 @@ void sendData() { } // sendData() void flushQueues(void) { + rcmd_queuereset(); #if (HAS_LORA) lora_queuereset(); #endif @@ -202,7 +202,7 @@ void flushQueues(void) { } bool allQueuesEmtpy(void) { - uint32_t rc = 0; + uint32_t rc = rcmd_queuewaiting(); #if (HAS_LORA) rc += lora_queuewaiting(); #endif From 571de8b9abaf75aacb3ae8bcf018663640124bfc Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 1 Jan 2021 15:55:22 +0100 Subject: [PATCH 089/104] make all queues static --- src/lorawan.cpp | 2 +- src/macsniff.cpp | 2 +- src/mqttclient.cpp | 2 +- src/rcommand.cpp | 2 +- src/spislave.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 440b4a47..d83eaca7 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -19,7 +19,7 @@ RTC_DATA_ATTR lmic_t RTC_LMIC; #endif #endif -QueueHandle_t LoraSendQueue; +static QueueHandle_t LoraSendQueue; TaskHandle_t lmicTask = NULL, lorasendTask = NULL; class MyHalConfig_t : public Arduino_LMIC::HalConfiguration_t { diff --git a/src/macsniff.cpp b/src/macsniff.cpp index e5044039..2ff27634 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -6,7 +6,7 @@ // Local logging tag static const char TAG[] = __FILE__; -QueueHandle_t MacQueue; +static QueueHandle_t MacQueue; TaskHandle_t macProcessTask; static uint32_t salt = renew_salt(); diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index 3be38ba9..89d54fde 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -4,7 +4,7 @@ static const char TAG[] = __FILE__; -QueueHandle_t MQTTSendQueue; +static QueueHandle_t MQTTSendQueue; TaskHandle_t mqttTask; Ticker mqttTimer; diff --git a/src/rcommand.cpp b/src/rcommand.cpp index b568b505..0426c475 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -5,7 +5,7 @@ // Local logging tag static const char TAG[] = __FILE__; -QueueHandle_t RcmdQueue; +static QueueHandle_t RcmdQueue; TaskHandle_t rcmdTask; // set of functions that can be triggered by remote commands diff --git a/src/spislave.cpp b/src/spislave.cpp index d0778384..4b2087d8 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -43,7 +43,7 @@ static const char TAG[] = __FILE__; DMA_ATTR uint8_t txbuf[BUFFER_SIZE]; DMA_ATTR uint8_t rxbuf[BUFFER_SIZE]; -QueueHandle_t SPISendQueue; +static QueueHandle_t SPISendQueue; TaskHandle_t spiTask; From f283758649890696bb500392e3473103a48762c3 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 1 Jan 2021 22:48:16 +0100 Subject: [PATCH 090/104] Code sanitization unneeded xTaskNotfiyFromISR --- src/cyclic.cpp | 2 +- src/senddata.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cyclic.cpp b/src/cyclic.cpp index ae26d4ce..8ccc3086 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -14,7 +14,7 @@ extern boolean isSDS011Active; #endif void setCyclicIRQ() { - xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL); + xTaskNotify(irqHandlerTask, CYCLIC_IRQ, eSetBits); } // do all housekeeping diff --git a/src/senddata.cpp b/src/senddata.cpp index 81aa2004..f5738552 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -4,7 +4,7 @@ Ticker sendTimer; void setSendIRQ() { - xTaskNotifyFromISR(irqHandlerTask, SENDCYCLE_IRQ, eSetBits, NULL); + xTaskNotify(irqHandlerTask, SENDCYCLE_IRQ, eSetBits); } // put data to send in RTos Queues used for transmit over channels Lora and SPI From 58a9e61ade137f5badced6e631b54ff2872fdf23 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Fri, 1 Jan 2021 22:48:37 +0100 Subject: [PATCH 091/104] main.cpp: corrections comments --- src/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 6bc041b3..19ea3fef 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -61,8 +61,10 @@ irqHandlerTask (Core 1), see irqhandler.cpp fired by hardware DisplayIRQ -> esp32 timer 0 -ButtonIRQ -> external gpio -PMUIRQ -> PMU chip gpio +CLOCKIRQ -> esp32 timer 1 or external GPIO (RTC_INT or GPS_INT) +MatrixDisplayIRQ-> esp32 timer 3 +ButtonIRQ -> external GPIO +PMUIRQ -> PMU chip GPIO fired by software (Ticker.h) TIMESYNC_IRQ -> setTimeSyncIRQ() From 2375489f92523b502f76f1b5600273a397341679 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 00:15:36 +0100 Subject: [PATCH 092/104] blescan.cpp: bugfix #if (BLECOUNTER) --- src/blecsan.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/blecsan.cpp b/src/blecsan.cpp index 8b8a8207..9b29d7a9 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -277,12 +277,11 @@ void start_BLEscan(void) { // Register callback function for capturing bluetooth packets register_ble_callback(false); ESP_LOGI(TAG, "Bluetooth scanner started"); -#endif // BLECOUNTER } else { ESP_LOGE(TAG, "Bluetooth controller start failed. Resetting device"); do_reset(true); } - +#endif // BLECOUNTER } // start_BLEscan void stop_BLEscan(void) { From 149bd7e05ebc651a64de51b19e798fdaa25a41db Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 00:16:12 +0100 Subject: [PATCH 093/104] rcommand.cpp: bugfix display queue size --- src/rcommand.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 0426c475..cebc1c3e 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -484,7 +484,8 @@ esp_err_t rcmd_init(void) { ESP_LOGE(TAG, "Could not create rcommand send queue. Aborting."); return ESP_FAIL; } - ESP_LOGI(TAG, "Rcommand send queue created, size %d Bytes", RCMD_QUEUE_SIZE); + ESP_LOGI(TAG, "Rcommand send queue created, size %d Bytes", + RCMD_QUEUE_SIZE * sizeof(RcmdBuffer_t)); xTaskCreatePinnedToCore(rcmd_process, // task function "rcmdloop", // name of task From d113c945b25c6ea213544cc4b5903c6c64ea7414 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 11:09:13 +0100 Subject: [PATCH 094/104] macsniff.cpp sanitizations --- src/macsniff.cpp | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/macsniff.cpp b/src/macsniff.cpp index 2ff27634..fb38d661 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -119,9 +119,7 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { int8_t beaconID = isBeacon(macConvert(MacBuffer.mac)); if (beaconID >= 0) { ESP_LOGI(TAG, "Beacon ID#%d detected", beaconID); -#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) blink_LED(COLOR_WHITE, 2000); -#endif payload.reset(); payload.addAlarm(MacBuffer.rssi, beaconID); SendPayload(BEACONPORT); @@ -156,30 +154,22 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { case MAC_SNIFF_WIFI: macs_wifi++; // increment Wifi MACs counter -#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) blink_LED(COLOR_GREEN, 50); -#endif break; -#if (BLECOUNTER) case MAC_SNIFF_BLE: macs_ble++; // increment BLE Macs counter -#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) blink_LED(COLOR_MAGENTA, 50); -#endif break; -#if (COUNT_ENS) case MAC_SNIFF_BLE_ENS: macs_ble++; // increment BLE Macs counter cwa_mac_add(hashedmac); // process ENS beacon -#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) blink_LED(COLOR_WHITE, 50); -#endif break; -#endif // COUNT_ENS -#endif // BLECOUNTER + default: + break; } // switch } // added From 41ab41566f30cd1496ad9571134343aea16c00a9 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 11:09:47 +0100 Subject: [PATCH 095/104] MAC container using DRAM --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 19ea3fef..3fb5c60b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -103,7 +103,7 @@ timesource_t timeSource = _unsynced; // container holding unique MAC address hashes with Memory Alloctor using PSRAM, // if present -std::set, Mallocator> macs; +DRAM_ATTR std::set, Mallocator> macs; // initialize payload encoder PayloadConvert payload(PAYLOAD_BUFFER_SIZE); From 399767e7ac3dcee1c9c5e8eaafa744a178b7777a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 11:34:13 +0100 Subject: [PATCH 096/104] add MQTT nodered example --- README.md | 2 +- src/Node-RED/MQTT.json | 467 ++++++++++++++++++ .../Timeserver.json} | 0 src/{Timeserver => Node-RED}/timeserver.java | 0 4 files changed, 468 insertions(+), 1 deletion(-) create mode 100644 src/Node-RED/MQTT.json rename src/{Timeserver/Nodered-Timeserver.json => Node-RED/Timeserver.json} (100%) rename src/{Timeserver => Node-RED}/timeserver.java (100%) diff --git a/README.md b/README.md index 0f0c0529..8631a383 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ Paxcounter supports a battery friendly power saving mode. In this mode the devic # Time sync -Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). Bonus: If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the enclosed [**Timeserver code**](/src/Timeserver/Nodered-Timeserver.json). Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using. +Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). Bonus: If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the enclosed [**Timeserver code**](/src/Node-RED/Timeserver.json). Configure MQTT nodes in Node-Red for the LORAWAN application used by paxocunter device. # Wall clock controller diff --git a/src/Node-RED/MQTT.json b/src/Node-RED/MQTT.json new file mode 100644 index 00000000..4d2bde74 --- /dev/null +++ b/src/Node-RED/MQTT.json @@ -0,0 +1,467 @@ +[ + { + "id": "7bc3eb4f.fe4b64", + "type": "mqtt in", + "z": "c26229c6.2d1ae8", + "name": "Olimex", + "topic": "paxout/#", + "qos": "2", + "datatype": "utf8", + "broker": "bd56b04d.db0ff", + "x": 190, + "y": 160, + "wires": [ + [ + "d0276db6.83c46" + ] + ] + }, + { + "id": "4f59740b.296b7c", + "type": "debug", + "z": "c26229c6.2d1ae8", + "name": "data", + "active": true, + "tosidebar": true, + "console": false, + "tostatus": false, + "complete": "payload", + "targetType": "msg", + "statusVal": "", + "statusType": "auto", + "x": 1050, + "y": 280, + "wires": [] + }, + { + "id": "f10f8199.25183", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "wifi", + "property": "payload", + "pattern": "l16, x16", + "x": 630, + "y": 100, + "wires": [ + [ + "800f5faa.15155" + ] + ] + }, + { + "id": "df4bdce5.dc219", + "type": "switch", + "z": "c26229c6.2d1ae8", + "name": "Ports", + "property": "topic", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "paxout/1", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/2", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/3", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/4", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/5", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/6", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/7", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/8", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/9", + "vt": "str" + }, + { + "t": "eq", + "v": "paxout/10", + "vt": "str" + }, + { + "t": "eq", + "v": "status", + "vt": "str" + } + ], + "checkall": "true", + "repair": false, + "outputs": 11, + "x": 370, + "y": 260, + "wires": [ + [ + "f10f8199.25183" + ], + [ + "e2974f79.55d5b" + ], + [ + "5f6987dd.b89378" + ], + [], + [ + "eaca83c4.5fcaf" + ], + [], + [], + [ + "7b6cbc9d.18fe44" + ], + [ + "d269537.4738fb" + ], + [ + "409e4762.71ff68" + ], + [ + "13fbfc5d.40dcc4" + ] + ] + }, + { + "id": "eaca83c4.5fcaf", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "button", + "property": "payload", + "pattern": "b8 => button", + "x": 630, + "y": 280, + "wires": [ + [ + "a6037445.eb8ac8" + ] + ] + }, + { + "id": "409e4762.71ff68", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "cwa", + "property": "payload", + "pattern": "l16 => cwa", + "x": 630, + "y": 140, + "wires": [ + [ + "2966fd1c.0b33d2" + ] + ] + }, + { + "id": "d0276db6.83c46", + "type": "base64", + "z": "c26229c6.2d1ae8", + "name": "Decode", + "action": "", + "property": "payload", + "x": 200, + "y": 260, + "wires": [ + [ + "df4bdce5.dc219" + ] + ] + }, + { + "id": "13fbfc5d.40dcc4", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "Connection", + "property": "payload", + "pattern": "b8[19]|str(\"ascii\")", + "x": 650, + "y": 440, + "wires": [ + [ + "4f59740b.296b7c" + ] + ] + }, + { + "id": "800f5faa.15155", + "type": "ui_chart", + "z": "c26229c6.2d1ae8", + "name": "pax", + "group": "6838565d.06bf08", + "order": 1, + "width": 0, + "height": 0, + "label": "Pax [Wifi]", + "chartType": "line", + "legend": "false", + "xformat": "dd HH:mm", + "interpolate": "linear", + "nodata": "Noch keine Daten", + "dot": false, + "ymin": "", + "ymax": "", + "removeOlder": 1, + "removeOlderPoints": "", + "removeOlderUnit": "3600", + "cutout": 0, + "useOneColor": false, + "useUTC": false, + "colors": [ + "#1f77b4", + "#aec7e8", + "#ff7f0e", + "#2ca02c", + "#98df8a", + "#d62728", + "#ff9896", + "#9467bd", + "#c5b0d5" + ], + "outputs": 1, + "x": 810, + "y": 100, + "wires": [ + [] + ] + }, + { + "id": "2966fd1c.0b33d2", + "type": "ui_chart", + "z": "c26229c6.2d1ae8", + "name": "cwa", + "group": "6838565d.06bf08", + "order": 1, + "width": 0, + "height": 0, + "label": "CWA", + "chartType": "line", + "legend": "false", + "xformat": "dd HH:mm", + "interpolate": "linear", + "nodata": "", + "dot": false, + "ymin": "", + "ymax": "", + "removeOlder": 1, + "removeOlderPoints": "", + "removeOlderUnit": "3600", + "cutout": 0, + "useOneColor": false, + "useUTC": false, + "colors": [ + "#1f77b4", + "#aec7e8", + "#ff7f0e", + "#2ca02c", + "#98df8a", + "#d62728", + "#ff9896", + "#9467bd", + "#c5b0d5" + ], + "outputs": 1, + "x": 810, + "y": 140, + "wires": [ + [] + ] + }, + { + "id": "a6037445.eb8ac8", + "type": "ui_toast", + "z": "c26229c6.2d1ae8", + "position": "top right", + "displayTime": "3", + "highlight": "", + "sendall": true, + "outputs": 0, + "ok": "OK", + "cancel": "", + "raw": false, + "topic": "Button", + "name": "Popup", + "x": 810, + "y": 280, + "wires": [] + }, + { + "id": "b4478f8.f8b747", + "type": "mqtt out", + "z": "c26229c6.2d1ae8", + "name": "Olimex", + "topic": "paxin", + "qos": "", + "retain": "", + "broker": "bd56b04d.db0ff", + "x": 190, + "y": 480, + "wires": [] + }, + { + "id": "e2974f79.55d5b", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "device status", + "property": "payload", + "pattern": "l16 => voltage,\nl64 => uptime,\nb8 => temperature,\nl32 => ram,\nb8 => reset0,\nb8 => reset1", + "x": 650, + "y": 200, + "wires": [ + [ + "4f59740b.296b7c" + ] + ] + }, + { + "id": "5f6987dd.b89378", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "device config", + "property": "payload", + "pattern": "b8 => loradr,\nb8 => txPower,\nl16 => rssilimit,\nb8 => sendcycle,\nb8 => wifichancycle,\nb8 => blescantime,\nb8 => rgblum,\nb8 => configmask,\nb8 => payloadmask,\nb8z|str(\"ascii\") => version", + "x": 650, + "y": 240, + "wires": [ + [ + "4f59740b.296b7c" + ] + ] + }, + { + "id": "7b6cbc9d.18fe44", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "battery", + "property": "payload", + "pattern": "l16 => voltage", + "x": 630, + "y": 360, + "wires": [ + [ + "4f59740b.296b7c" + ] + ] + }, + { + "id": "d269537.4738fb", + "type": "binary", + "z": "c26229c6.2d1ae8", + "name": "time", + "property": "payload", + "pattern": "l32 => unixtime", + "x": 630, + "y": 400, + "wires": [ + [ + "4f59740b.296b7c" + ] + ] + }, + { + "id": "ce0cb32b.b7b96", + "type": "base64", + "z": "c26229c6.2d1ae8", + "name": "Encode", + "action": "", + "property": "payload", + "x": 200, + "y": 420, + "wires": [ + [ + "b4478f8.f8b747" + ] + ] + }, + { + "id": "c246a129.eeb5e", + "type": "inject", + "z": "c26229c6.2d1ae8", + "name": "rcmd", + "props": [ + { + "p": "payload" + }, + { + "p": "topic", + "vt": "str" + } + ], + "repeat": "", + "crontab": "", + "once": false, + "onceDelay": 0.1, + "topic": "paxin", + "payload": "[128, 129]", + "payloadType": "bin", + "x": 190, + "y": 360, + "wires": [ + [ + "ce0cb32b.b7b96" + ] + ] + }, + { + "id": "bd56b04d.db0ff", + "type": "mqtt-broker", + "name": "Shiftr.io", + "broker": "public.cloud.shiftr.io", + "port": "1883", + "clientid": "", + "usetls": false, + "compatmode": false, + "keepalive": "60", + "cleansession": true, + "birthTopic": "paxout/status", + "birthQos": "0", + "birthPayload": "on", + "closeTopic": "paxout/status", + "closeQos": "0", + "closePayload": "off", + "willTopic": "paxout/status", + "willQos": "0", + "willPayload": "unknown" + }, + { + "id": "6838565d.06bf08", + "type": "ui_group", + "name": "MQTT", + "tab": "ad5894fa.466c58", + "order": 1, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "ad5894fa.466c58", + "type": "ui_tab", + "name": "Paxcounter", + "icon": "people", + "order": 4 + } +] \ No newline at end of file diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Node-RED/Timeserver.json similarity index 100% rename from src/Timeserver/Nodered-Timeserver.json rename to src/Node-RED/Timeserver.json diff --git a/src/Timeserver/timeserver.java b/src/Node-RED/timeserver.java similarity index 100% rename from src/Timeserver/timeserver.java rename to src/Node-RED/timeserver.java From 11dddf459b914198df660e6b1e0aba365f0f41a8 Mon Sep 17 00:00:00 2001 From: HouzuoGuo Date: Sun, 3 Jan 2021 13:09:40 +0200 Subject: [PATCH 097/104] Fix incorrect prefix W/E that was displayed along with longitude --- src/display.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/display.cpp b/src/display.cpp index 4ccb1b7e..3b7bb01b 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -400,7 +400,7 @@ void dp_drawPage(time_t t, bool nextpage) { gps.location.lat()); // line 6-7: GPS longitude - dp_printf("%c%07.4f", gps.location.rawLat().negative ? 'W' : 'E', + dp_printf("%c%07.4f", gps.location.rawLng().negative ? 'W' : 'E', gps.location.lng()); } else { From e8cbc4c6272f33652185766489417aa1812fa1f2 Mon Sep 17 00:00:00 2001 From: HouzuoGuo Date: Sun, 3 Jan 2021 17:24:28 +0200 Subject: [PATCH 098/104] Avoid invoking ENS function in macsniff.cpp when ENS support is disabled --- src/macsniff.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/macsniff.cpp b/src/macsniff.cpp index fb38d661..3bce9453 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -161,13 +161,13 @@ uint16_t mac_analyze(MacBuffer_t MacBuffer) { macs_ble++; // increment BLE Macs counter blink_LED(COLOR_MAGENTA, 50); break; - +#if (COUNT_ENS) case MAC_SNIFF_BLE_ENS: macs_ble++; // increment BLE Macs counter cwa_mac_add(hashedmac); // process ENS beacon blink_LED(COLOR_WHITE, 50); break; - +#endif default: break; From 690c843ccf1938a3452e544f7c536eef818c7e41 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 18:00:51 +0100 Subject: [PATCH 099/104] new rcmd load/save config --- README.md | 10 ++++++++- src/rcommand.cpp | 55 +++++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8631a383..5c222b3e 100644 --- a/README.md +++ b/README.md @@ -366,7 +366,7 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering. The device listenes for remote control commands on LoRaWAN Port 2. Multiple commands per downlink are possible by concatenating them, but must not exceed a maximum of 10 bytes per downlink. -Note: all settings are stored in NVRAM and will be reloaded when device starts. +Note: settings can be stored in NVRAM to make them persistant (reloaded during device startup / restart). To store settings, use command 0x20. Send for example `8386` as Downlink on Port 2 to get battery status and time/date from the device. @@ -525,6 +525,14 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0 ... 255 device sleep cycle in seconds/2 e.g. 120 -> device sleeps 240 seconds after each send cycle [default = 0] +0x20 store device configuration + + Current device runtime configuration is stored in NVRAM, will be reloaded after restart + +0x21 load device configuration + + Current device runtime configuration will be loaded from NVRAM, replacing current settings immediately (use with care!) + 0x80 get device configuration Device answers with it's current configuration on Port 3. diff --git a/src/rcommand.cpp b/src/rcommand.cpp index cebc1c3e..1f36b113 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -369,28 +369,37 @@ void set_enscount(uint8_t val[]) { cfg.payloadmask &= ~SENSOR1_DATA; } +void set_loadconfig(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: load config from NVRAM"); + loadConfig(); +}; + +void set_saveconfig(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: save config to NVRAM"); + saveConfig(false); +}; + // assign previously defined functions to set of numeric remote commands -// format: opcode, function, #bytes params, -// flag (true = do make settings persistent / false = don't) -// +// format: {opcode, function, number of function arguments} static const cmd_t table[] = { - {0x01, set_rssi, 1, true}, {0x02, set_countmode, 1, true}, - {0x03, set_gps, 1, true}, {0x04, set_display, 1, true}, - {0x05, set_loradr, 1, true}, {0x06, set_lorapower, 1, true}, - {0x07, set_loraadr, 1, true}, {0x08, set_screensaver, 1, true}, - {0x09, set_reset, 1, false}, {0x0a, set_sendcycle, 1, true}, - {0x0b, set_wifichancycle, 1, true}, {0x0c, set_blescantime, 1, true}, - {0x0d, set_macfilter, 1, false}, {0x0e, set_blescan, 1, true}, - {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, - {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, - {0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true}, - {0x15, set_bme, 1, true}, {0x16, set_batt, 1, true}, - {0x17, set_wifiscan, 1, true}, {0x18, set_enscount, 1, true}, - {0x19, set_sleepcycle, 1, true}, {0x80, get_config, 0, false}, - {0x81, get_status, 0, false}, {0x83, get_batt, 0, false}, - {0x84, get_gps, 0, false}, {0x85, get_bme, 0, false}, - {0x86, get_time, 0, false}, {0x87, set_time, 0, false}, - {0x99, set_flush, 0, false}}; + {0x01, set_rssi, 1}, {0x02, set_countmode, 1}, + {0x03, set_gps, 1}, {0x04, set_display, 1}, + {0x05, set_loradr, 1}, {0x06, set_lorapower, 1}, + {0x07, set_loraadr, 1}, {0x08, set_screensaver, 1}, + {0x09, set_reset, 1}, {0x0a, set_sendcycle, 1}, + {0x0b, set_wifichancycle, 1}, {0x0c, set_blescantime, 1}, + {0x0d, set_macfilter, 1}, {0x0e, set_blescan, 1}, + {0x0f, set_wifiant, 1}, {0x10, set_rgblum, 1}, + {0x11, set_monitor, 1}, {0x12, set_beacon, 7}, + {0x13, set_sensor, 2}, {0x14, set_payloadmask, 1}, + {0x15, set_bme, 1}, {0x16, set_batt, 1}, + {0x17, set_wifiscan, 1}, {0x18, set_enscount, 1}, + {0x19, set_sleepcycle, 1}, {0x20, set_loadconfig, 0}, + {0x21, set_saveconfig, 0}, {0x80, get_config, 0}, + {0x81, get_status, 0}, {0x83, get_batt, 0}, + {0x84, get_gps, 0}, {0x85, get_bme, 0}, + {0x86, get_time, 0}, {0x87, set_time, 0}, + {0x99, set_flush, 0}}; static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table @@ -402,7 +411,6 @@ void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { return; uint8_t foundcmd[cmdlength], cursor = 0; - bool storeflag = false; while (cursor < cmdlength) { @@ -414,8 +422,6 @@ void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { memmove(foundcmd, cmd + cursor, table[i].params); // strip opcode from cmd array cursor += table[i].params; - if (table[i].store) // ceck if function needs to store configuration - storeflag = true; table[i].func( foundcmd); // execute assigned function with given parameters } else @@ -432,9 +438,6 @@ void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { } } // command parsing loop - if (storeflag) - saveConfig(); - } // rcmd_execute() // remote command processing task From cb0680b623942a71603a72f6d8c94a6bf5b70d6e Mon Sep 17 00:00:00 2001 From: HouzuoGuo Date: Sun, 3 Jan 2021 19:25:11 +0200 Subject: [PATCH 100/104] When GPZDA time-of-day is considered valid by TinyGps lib, the date is also valid, though not indicated by return value of isValid() --- src/gpsread.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 00e3c90a..3b764b55 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -109,7 +109,7 @@ time_t get_gpstime(uint16_t *msec) { #endif // did we get a current date & time? - if (gpstime.isValid() && gpsday.isValid()) { + if (gpstime.isValid()) { time_t t = 0; tmElements_t tm; @@ -188,4 +188,4 @@ void gps_loop(void *pvParameters) { } // gps_loop() -#endif // HAS_GPS \ No newline at end of file +#endif // HAS_GPS From 2352d7d7431e6116f902cc62d8d0c0fb80867b6c Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 21:52:55 +0100 Subject: [PATCH 101/104] add ENS compile warning if no BLE --- src/corona.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/corona.cpp b/src/corona.cpp index a8dd85a4..bd20fb17 100644 --- a/src/corona.cpp +++ b/src/corona.cpp @@ -5,6 +5,10 @@ // (c) by Kaspar Metz // modified for use in the Paxcounter by AQ +#if (COUNT_ENS) && !(BLECOUNTER) +#warning ENS-Counter needs Bluetooth, but Bluetooth compile option is disabled +#endif + #if (COUNT_ENS) // Local logging tag From 59e38762969d198b95b4f6bb932068cbef0bbeb8 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Sun, 3 Jan 2021 22:40:49 +0100 Subject: [PATCH 102/104] rcommand.cpp: code sanitization --- src/rcommand.cpp | 58 ++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 1f36b113..9181cdd2 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -379,42 +379,41 @@ void set_saveconfig(uint8_t val[]) { saveConfig(false); }; -// assign previously defined functions to set of numeric remote commands -// format: {opcode, function, number of function arguments} -static const cmd_t table[] = { - {0x01, set_rssi, 1}, {0x02, set_countmode, 1}, - {0x03, set_gps, 1}, {0x04, set_display, 1}, - {0x05, set_loradr, 1}, {0x06, set_lorapower, 1}, - {0x07, set_loraadr, 1}, {0x08, set_screensaver, 1}, - {0x09, set_reset, 1}, {0x0a, set_sendcycle, 1}, - {0x0b, set_wifichancycle, 1}, {0x0c, set_blescantime, 1}, - {0x0d, set_macfilter, 1}, {0x0e, set_blescan, 1}, - {0x0f, set_wifiant, 1}, {0x10, set_rgblum, 1}, - {0x11, set_monitor, 1}, {0x12, set_beacon, 7}, - {0x13, set_sensor, 2}, {0x14, set_payloadmask, 1}, - {0x15, set_bme, 1}, {0x16, set_batt, 1}, - {0x17, set_wifiscan, 1}, {0x18, set_enscount, 1}, - {0x19, set_sleepcycle, 1}, {0x20, set_loadconfig, 0}, - {0x21, set_saveconfig, 0}, {0x80, get_config, 0}, - {0x81, get_status, 0}, {0x83, get_batt, 0}, - {0x84, get_gps, 0}, {0x85, get_bme, 0}, - {0x86, get_time, 0}, {0x87, set_time, 0}, - {0x99, set_flush, 0}}; - -static const uint8_t cmdtablesize = - sizeof(table) / sizeof(table[0]); // number of commands in command table - // check and execute remote command void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { + // assign previously defined functions to set of numeric remote commands + // format: {opcode, function, number of function arguments} + + const cmd_t table[] = { + {0x01, set_rssi, 1}, {0x02, set_countmode, 1}, + {0x03, set_gps, 1}, {0x04, set_display, 1}, + {0x05, set_loradr, 1}, {0x06, set_lorapower, 1}, + {0x07, set_loraadr, 1}, {0x08, set_screensaver, 1}, + {0x09, set_reset, 1}, {0x0a, set_sendcycle, 1}, + {0x0b, set_wifichancycle, 1}, {0x0c, set_blescantime, 1}, + {0x0d, set_macfilter, 1}, {0x0e, set_blescan, 1}, + {0x0f, set_wifiant, 1}, {0x10, set_rgblum, 1}, + {0x11, set_monitor, 1}, {0x12, set_beacon, 7}, + {0x13, set_sensor, 2}, {0x14, set_payloadmask, 1}, + {0x15, set_bme, 1}, {0x16, set_batt, 1}, + {0x17, set_wifiscan, 1}, {0x18, set_enscount, 1}, + {0x19, set_sleepcycle, 1}, {0x20, set_loadconfig, 0}, + {0x21, set_saveconfig, 0}, {0x80, get_config, 0}, + {0x81, get_status, 0}, {0x83, get_batt, 0}, + {0x84, get_gps, 0}, {0x85, get_bme, 0}, + {0x86, get_time, 0}, {0x87, set_time, 0}, + {0x99, set_flush, 0}}; + if (cmdlength == 0) return; uint8_t foundcmd[cmdlength], cursor = 0; + int i = + sizeof(table) / sizeof(table[0]); // number of commands in command table while (cursor < cmdlength) { - int i = cmdtablesize; while (i--) { if (cmd[cursor] == table[i].opcode) { // lookup command in opcode table cursor++; // strip 1 byte opcode @@ -429,9 +428,10 @@ void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { "Remote command x%02X called with missing parameter(s), " "skipped", table[i].opcode); - break; // command found -> exit table lookup loop - } // end of command validation - } // end of command table lookup loop + break; // command found -> exit table lookup loop + } // end of command validation + } // end of command table lookup loop + if (i < 0) { // command not found -> exit parser ESP_LOGI(TAG, "Unknown remote command x%02X, ignored", cmd[cursor]); break; From 3d6af69c0b41a0ca626586bed5b8156119f82be7 Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 7 Jan 2021 10:37:55 +0100 Subject: [PATCH 103/104] repair broken rcommand processing --- include/rcommand.h | 1 - src/rcommand.cpp | 52 ++++++++++++++++++++++++---------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/include/rcommand.h b/include/rcommand.h index 696a23d2..8149d668 100644 --- a/include/rcommand.h +++ b/include/rcommand.h @@ -25,7 +25,6 @@ typedef struct { const uint8_t opcode; void (*func)(uint8_t[]); const uint8_t params; - const bool store; } cmd_t; // Struct for remote command processing queue diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 9181cdd2..d39ba1f3 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -379,41 +379,43 @@ void set_saveconfig(uint8_t val[]) { saveConfig(false); }; +// assign previously defined functions to set of numeric remote commands +// format: {opcode, function, number of function arguments} + +static const cmd_t table[] = { + {0x01, set_rssi, 1}, {0x02, set_countmode, 1}, + {0x03, set_gps, 1}, {0x04, set_display, 1}, + {0x05, set_loradr, 1}, {0x06, set_lorapower, 1}, + {0x07, set_loraadr, 1}, {0x08, set_screensaver, 1}, + {0x09, set_reset, 1}, {0x0a, set_sendcycle, 1}, + {0x0b, set_wifichancycle, 1}, {0x0c, set_blescantime, 1}, + {0x0d, set_macfilter, 1}, {0x0e, set_blescan, 1}, + {0x0f, set_wifiant, 1}, {0x10, set_rgblum, 1}, + {0x11, set_monitor, 1}, {0x12, set_beacon, 7}, + {0x13, set_sensor, 2}, {0x14, set_payloadmask, 1}, + {0x15, set_bme, 1}, {0x16, set_batt, 1}, + {0x17, set_wifiscan, 1}, {0x18, set_enscount, 1}, + {0x19, set_sleepcycle, 1}, {0x20, set_loadconfig, 0}, + {0x21, set_saveconfig, 0}, {0x80, get_config, 0}, + {0x81, get_status, 0}, {0x83, get_batt, 0}, + {0x84, get_gps, 0}, {0x85, get_bme, 0}, + {0x86, get_time, 0}, {0x87, set_time, 0}, + {0x99, set_flush, 0}}; + +static const uint8_t cmdtablesize = + sizeof(table) / sizeof(table[0]); // number of commands in command table + // check and execute remote command void rcmd_execute(const uint8_t cmd[], const uint8_t cmdlength) { - // assign previously defined functions to set of numeric remote commands - // format: {opcode, function, number of function arguments} - - const cmd_t table[] = { - {0x01, set_rssi, 1}, {0x02, set_countmode, 1}, - {0x03, set_gps, 1}, {0x04, set_display, 1}, - {0x05, set_loradr, 1}, {0x06, set_lorapower, 1}, - {0x07, set_loraadr, 1}, {0x08, set_screensaver, 1}, - {0x09, set_reset, 1}, {0x0a, set_sendcycle, 1}, - {0x0b, set_wifichancycle, 1}, {0x0c, set_blescantime, 1}, - {0x0d, set_macfilter, 1}, {0x0e, set_blescan, 1}, - {0x0f, set_wifiant, 1}, {0x10, set_rgblum, 1}, - {0x11, set_monitor, 1}, {0x12, set_beacon, 7}, - {0x13, set_sensor, 2}, {0x14, set_payloadmask, 1}, - {0x15, set_bme, 1}, {0x16, set_batt, 1}, - {0x17, set_wifiscan, 1}, {0x18, set_enscount, 1}, - {0x19, set_sleepcycle, 1}, {0x20, set_loadconfig, 0}, - {0x21, set_saveconfig, 0}, {0x80, get_config, 0}, - {0x81, get_status, 0}, {0x83, get_batt, 0}, - {0x84, get_gps, 0}, {0x85, get_bme, 0}, - {0x86, get_time, 0}, {0x87, set_time, 0}, - {0x99, set_flush, 0}}; - if (cmdlength == 0) return; uint8_t foundcmd[cmdlength], cursor = 0; - int i = - sizeof(table) / sizeof(table[0]); // number of commands in command table while (cursor < cmdlength) { + int i = cmdtablesize; while (i--) { if (cmd[cursor] == table[i].opcode) { // lookup command in opcode table cursor++; // strip 1 byte opcode From dee4d825a06f71a9566ee1d95b625e8ecde5d15a Mon Sep 17 00:00:00 2001 From: cyberman54 Date: Thu, 7 Jan 2021 22:07:55 +0100 Subject: [PATCH 104/104] bugfix i2c gps broken (issue #712) --- src/gpsread.cpp | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 3b764b55..a29d9c9f 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -30,34 +30,27 @@ static uint16_t nmea_txDelay_ms = 0; // initialize and configure GPS int gps_init(void) { - int ret = 1; - if (!gps_config()) { ESP_LOGE(TAG, "GPS chip initializiation error"); return 0; } #ifdef GPS_SERIAL + ESP_LOGI(TAG, "Opening serial GPS"); GPS_Serial.begin(GPS_SERIAL); - ESP_LOGI(TAG, "Using serial GPS"); #elif defined GPS_I2C + ESP_LOGI(TAG, "Opening I2C GPS"); Wire.begin(GPS_I2C, 400000); // I2C connect to GPS device with 400 KHz Wire.beginTransmission(GPS_ADDR); - Wire.write(0x00); // dummy write - ret = Wire.endTransmission(); // check if chip is seen on i2c bus - - if (ret) { - ESP_LOGE(TAG, - "Quectel L76 GPS chip not found on i2c bus, bus error %d. " - "Stopping GPS-Task.", - ret); - ret = 0; - } else { + Wire.write(0x00); // dummy write + if (Wire.endTransmission()) { + ESP_LOGE(TAG, "Quectel L76 GPS chip not found"); + return 0; + } else ESP_LOGI(TAG, "Quectel L76 GPS chip found"); - } #endif - return ret; + return 1; } // gps_init() // detect gps chipset type and configure it with device specific settings