diff --git a/README.md b/README.md index a9f2ebed..eaea51cd 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,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) - Generic ESP32 Depending on board hardware following features are supported: @@ -58,7 +59,7 @@ Depending on board hardware following features are supported: - Real Time Clock (Maxim DS3231 I2C) - IF482 (serial) and DCF77 (gpio) time telegram generator - Switch external power / battery -- 64x16 pixel LED Matrix display (similar to [this 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)) +- 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)) Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/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.
@@ -155,9 +156,9 @@ If you're using a device with OLED display, or if you add such one to the I2C bu You can add up to 3 user defined sensors. Insert sensor's payload scheme in [*sensor.cpp*](src/sensor.cpp). Bosch BME280 / BME680 environment sensors are supported. Enable flag *lib_deps_sensors* for your board in [*platformio.ini*](src/platformio.ini) and configure BME in board's hal file before build. If you need Bosch's proprietary BSEC libraray (e.g. to get indoor air quality value from BME680) further enable *build_flags_sensors*, which comes on the price of reduced RAM and increased build size. RTC DS3231, generic serial NMEA GPS, I2C LoPy GPS are supported, and to be configured in board's hal file. See [*generic.h*](src/hal/generic.h) for all options and for proper configuration of BME280/BME680. -Output of user sensor data can be switched by user remote control command 0x13 sent to Port 2. +Output of user sensor data can be switched by user remote control command 0x14 sent to Port 2. -Output of sensor and peripheral data is internally switched by a bitmask register. Default mask (0xFF) can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme: +Output of sensor and peripheral data is internally switched by a bitmask register. Default mask can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme: | Bit | Sensordata | | --- | ------------- | @@ -168,7 +169,7 @@ Output of sensor and peripheral data is internally switched by a bitmask registe | 4 | User sensor 1 | | 5 | User sensor 2 | | 6 | User sensor 3 | -| 7 | reserved | +| 7 | Batterylevel | # Time sync @@ -304,21 +305,36 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts. 0 = display off 1 = display on [default] -0x05 set LoRa spread factor +0x05 set LoRa datarate - 7 ... 12 [default: 9] + 0 ... 15 see LoRaWAN regional parameters for details [default: 5] + + Example for EU868: + + DataRate Configuration Bit/s + 0 LoRa: SF12 / 125 kHz 250 + 1 LoRa: SF11 / 125 kHz 440 + 2 LoRa: SF10 / 125 kHz 980 + 3 LoRa: SF9 / 125 kHz 1760 + 4 LoRa: SF8 / 125 kHz 3125 + 5 LoRa: SF7 / 125 kHz 5470 + 6* LoRa: SF7 / 250 kHz 11000 + 7* FSK: 50 kbps 50000 + 8 .. 14 reserved for future use (RFU) + 15 ignored (device keeps current setting) + + *) not supported by TheThingsNetwork 0x06 set LoRa TXpower - 2 ... 15 [default: 15] + 0 ... 255 desired TX power in dBm [default: 14] 0x07 set LoRa Adaptive Data Rate mode 0 = ADR off 1 = ADR on [default] - Note: set ADR to off, if device is moving, set to on, if not. - If ADR is set to on, SF value is shown inverted on display. + If ADR is set to off, SF value is shown inverted on display. 0x08 do nothing @@ -382,17 +398,35 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts. byte 1 = user sensor number (1..3) byte 2 = sensor mode (0 = disabled / 1 = enabled [default]) +0x14 set payload mask + + byte 1 = sensor data payload mask (0..255, meaning of bits see above) + +0x15 set BME data on/off + + 0 = BME data off + 1 = BME data on, sends BME data on port 7 [default] + +0x16 set battery data on/off + + 0 = battery data off [default] + 1 = battery data on, sends voltage on port 8 + 0x80 get device configuration Device answers with it's current configuration on Port 3. 0x81 get device status - Device answers with it's current status on Port 2. + Device answers with it's current status on Port 2. + +0x83 get battery status + + Device answers with battery voltage on Port 8. 0x84 get device GPS status - Device answers with it's current status on Port 4. + Device answers with it's current status on Port 4. 0x85 get BME280 / BME680 sensor data diff --git a/build.py b/build.py index 52975106..685ada36 100644 --- a/build.py +++ b/build.py @@ -7,15 +7,15 @@ import os.path import requests from os.path import basename from platformio import util +from SCons.Script import DefaultEnvironment try: import configparser except ImportError: import ConfigParser as configparser -Import("env") - # get platformio environment variables +env = DefaultEnvironment() config = configparser.ConfigParser() config.read("platformio.ini") @@ -66,7 +66,15 @@ myboard = mykeys["board"] myuploadspeed = mykeys["upload_speed"] env.Replace(BOARD=myboard) env.Replace(UPLOAD_SPEED=myuploadspeed) + +# re-set partition table +mypartitiontable = config.get("env", "board_build.partitions") +board = env.BoardConfig(myboard) +board.manifest['build']['partitions'] = mypartitiontable + +# display target print('\033[94m' + "TARGET BOARD: " + myboard + " @ " + myuploadspeed + "bps" + '\033[0m') +print('\033[94m' + "Partition table: " + mypartitiontable + '\033[0m') # parse ota key file with open(otakeyfile) as myfile: diff --git a/include/globals.h b/include/globals.h index e8e8bb48..a753d5fb 100644 --- a/include/globals.h +++ b/include/globals.h @@ -42,12 +42,16 @@ #define SCREEN_MODE (0x80) // I2C bus access control -#define I2C_MUTEX_LOCK() (xSemaphoreTake(I2Caccess, pdMS_TO_TICKS(10)) == pdTRUE) +#define I2C_MUTEX_LOCK() \ + (xSemaphoreTake(I2Caccess, pdMS_TO_TICKS(10)) == pdTRUE) #define I2C_MUTEX_UNLOCK() (xSemaphoreGive(I2Caccess)) +enum sendprio_t { prio_low, prio_normal, prio_high }; +enum timesource_t { _gps, _rtc, _lora, _unsynced }; + // Struct holding devices's runtime configuration typedef struct { - uint8_t lorasf; // 7-12, lora spreadfactor + uint8_t loradr; // 0-15, lora datarate uint8_t txpower; // 2-15, lora tx power uint8_t adrmode; // 0=disabled, 1=enabled uint8_t screensaver; // 0=disabled, 1=enabled @@ -73,6 +77,7 @@ typedef struct { typedef struct { uint8_t MessageSize; uint8_t MessagePort; + sendprio_t MessagePrio; uint8_t Message[PAYLOAD_BUFFER_SIZE]; } MessageBuffer_t; @@ -95,16 +100,13 @@ typedef struct { float gas; // raw gas sensor signal } bmeStatus_t; -enum sendprio_t { prio_low, prio_normal, prio_high }; -enum timesource_t { _gps, _rtc, _lora, _unsynced }; - extern std::set, Mallocator> macs; extern std::array::iterator it; extern std::array beacons; -extern configData_t cfg; // current device configuration -extern char display_line6[], display_line7[]; // screen buffers -extern uint8_t volatile channel; // wifi channel rotation counter +extern configData_t cfg; // current device configuration +extern char lmic_event_msg[]; // display buffer +extern uint8_t volatile channel; // wifi channel rotation counter extern uint16_t volatile macs_total, macs_wifi, macs_ble, batt_voltage; // display values extern bool volatile TimePulseTick; // 1sec pps flag set by GPS or RTC @@ -120,6 +122,7 @@ extern time_t userUTCTime; #include "led.h" #include "payload.h" #include "blescan.h" +#include "power.h" #if (HAS_GPS) #include "gpsread.h" @@ -141,10 +144,6 @@ extern time_t userUTCTime; #include "button.h" #endif -#ifdef BAT_MEASURE_ADC -#include "battery.h" -#endif - #ifdef HAS_ANTENNA_SWITCH #include "antenna.h" #endif diff --git a/include/i2cscan.h b/include/i2cscan.h new file mode 100644 index 00000000..38487e76 --- /dev/null +++ b/include/i2cscan.h @@ -0,0 +1,16 @@ +#ifndef _I2CSCAN_H +#define _I2CSCAN_H + +#include + +#define SSD1306_PRIMARY_ADDRESS (0x3D) +#define SSD1306_SECONDARY_ADDRESS (0x3C) +#define BME_PRIMARY_ADDRESS (0x77) +#define BME_SECONDARY_ADDRESS (0x76) +#define AXP192_PRIMARY_ADDRESS (0x34) +#define MCP_24AA02E64_PRIMARY_ADDRESS (0x50) +#define QUECTEL_GPS_PRIMARY_ADDRESS (0x10) + +int i2c_scan(void); + +#endif \ No newline at end of file diff --git a/include/irqhandler.h b/include/irqhandler.h index 08d6bd8c..193e32d5 100644 --- a/include/irqhandler.h +++ b/include/irqhandler.h @@ -10,12 +10,14 @@ #define UNMASK_IRQ 0x040 #define BME_IRQ 0x080 #define MATRIX_DISPLAY_IRQ 0x100 +#define PMU_IRQ 0x200 #include "globals.h" #include "cyclic.h" #include "senddata.h" #include "timekeeper.h" #include "bmesensor.h" +#include "power.h" void irqHandler(void *pvParameters); void mask_user_IRQ(); @@ -33,5 +35,9 @@ void IRAM_ATTR MatrixDisplayIRQ(); void IRAM_ATTR ButtonIRQ(); #endif +#ifdef HAS_PMU +void IRAM_ATTR PMUIRQ(); +#endif + #endif \ No newline at end of file diff --git a/include/ledmatrixdisplay.h b/include/ledmatrixdisplay.h index 9adfcd11..b7ec5a3b 100644 --- a/include/ledmatrixdisplay.h +++ b/include/ledmatrixdisplay.h @@ -13,5 +13,6 @@ void refreshTheMatrixDisplay(bool nextPage = false); void DrawNumber(String strNum, uint8_t iDotPos = 0); uint8_t GetCharFromFont(char cChar); uint8_t GetCharWidth(char cChar); +void ScrollLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows); #endif \ No newline at end of file diff --git a/include/lorawan.h b/include/lorawan.h index b3331751..a69aa4ba 100644 --- a/include/lorawan.h +++ b/include/lorawan.h @@ -4,7 +4,7 @@ #include "globals.h" #include "rcommand.h" #include "timekeeper.h" -#if(TIME_SYNC_LORASERVER) +#if (TIME_SYNC_LORASERVER) #include "timesync.h" #endif @@ -21,7 +21,14 @@ #endif extern QueueHandle_t LoraSendQueue; -extern TaskHandle_t lmicTask; +extern TaskHandle_t lmicTask, lorasendTask; + +// table of LORAWAN MAC commands +typedef struct { + const uint8_t opcode; + const char cmdname[20]; + const uint8_t params; +} mac_t; esp_err_t lora_stack_init(); void lmictask(void *pvParameters); @@ -33,10 +40,19 @@ void os_getDevKey(u1_t *buf); void os_getArtEui(u1_t *buf); void os_getDevEui(u1_t *buf); void showLoraKeys(void); -void switch_lora(uint8_t sf, uint8_t tx); -void lora_send(osjob_t *job); -void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio); +void lora_send(void *pvParameters); +void lora_enqueuedata(MessageBuffer_t *message); void lora_queuereset(void); +void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, + size_t nMsg); +void myTxCallback(void *pUserData, int fSuccess); +void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, const mac_t table[], + const uint8_t tablesize); +uint8_t getBattLevel(void); +const char *getSfName(rps_t rps); +const char *getBwName(rps_t rps); +const char *getCrName(rps_t rps); + #if (TIME_SYNC_LORAWAN) void user_request_network_time_callback(void *pVoidUserUTCTime, int flagSuccess); diff --git a/include/main.h b/include/main.h index 215aa47e..ebb1d440 100644 --- a/include/main.h +++ b/include/main.h @@ -1,12 +1,14 @@ #ifndef _MAIN_H #define _MAIN_H -#include // needed for reading ESP32 chip attributes -#include // needed for Wifi event handler +#include // needed for reading ESP32 chip attributes +#include // needed for Wifi event handler #include // needed for timers -#include // needed for showing coex sw version +#include // needed for showing coex sw version #include "globals.h" +#include "power.h" +#include "i2cscan.h" #include "blescan.h" #include "wifiscan.h" #include "configmanager.h" diff --git a/include/ota.h b/include/ota.h index 41ab5767..59bf876c 100644 --- a/include/ota.h +++ b/include/ota.h @@ -4,7 +4,6 @@ #ifdef USE_OTA #include "globals.h" -#include "battery.h" #include #include #include diff --git a/include/battery.h b/include/power.h similarity index 54% rename from include/battery.h rename to include/power.h index b4a83676..1ef571e1 100644 --- a/include/battery.h +++ b/include/power.h @@ -1,8 +1,10 @@ -#ifndef _BATTERY_H -#define _BATTERY_H +#ifndef _POWER_H +#define _POWER_H +#include #include #include +#include "i2cscan.h" #define DEFAULT_VREF 1100 // tbd: use adc2_vref_to_gpio() for better estimate #define NO_OF_SAMPLES 64 // we do some multisampling to get better values @@ -11,4 +13,12 @@ uint16_t read_voltage(void); void calibrate_voltage(void); bool batt_sufficient(void); -#endif +#ifdef HAS_PMU +#include +void power_event_IRQ(void); +void AXP192_power(bool on); +void AXP192_init(void); +void AXP192_showstatus(void); +#endif // HAS_PMU + +#endif \ No newline at end of file diff --git a/include/rcommand.h b/include/rcommand.h index e964eb85..78e1bae4 100644 --- a/include/rcommand.h +++ b/include/rcommand.h @@ -19,11 +19,11 @@ typedef struct { const uint8_t opcode; void (*func)(uint8_t []); - uint8_t params; + const uint8_t params; const bool store; } cmd_t; -void rcommand(uint8_t cmd[], uint8_t cmdlength); +void rcommand(const uint8_t cmd[], const uint8_t cmdlength); void do_reset(); #endif diff --git a/include/senddata.h b/include/senddata.h index d81b852b..4a748445 100644 --- a/include/senddata.h +++ b/include/senddata.h @@ -10,7 +10,7 @@ extern Ticker sendcycler; void SendPayload(uint8_t port, sendprio_t prio); -void sendCounter(void); +void sendData(void); void checkSendQueues(void); void flushQueues(); void sendcycle(void); diff --git a/include/spislave.h b/include/spislave.h index 44c58455..0d85a09e 100644 --- a/include/spislave.h +++ b/include/spislave.h @@ -28,7 +28,7 @@ licenses. Refer to LICENSE.txt file in repository for more details. esp_err_t spi_init(); -void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio); +void spi_enqueuedata(MessageBuffer_t *message); void spi_queuereset(); #endif // _SPISLAVE_H diff --git a/include/timekeeper.h b/include/timekeeper.h index 6239f082..20199ac1 100644 --- a/include/timekeeper.h +++ b/include/timekeeper.h @@ -27,6 +27,7 @@ void timeSync(void); uint8_t timepulse_init(void); time_t timeIsValid(time_t const t); void calibrateTime(void); +void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, timesource_t mytimesource); time_t compiledUTC(void); TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPins); diff --git a/include/timesync.h b/include/timesync.h index 1f431acb..83019a83 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -12,9 +12,8 @@ void timesync_init(void); void send_timesync_req(void); -int recv_timesync_ans(uint8_t buf[], uint8_t buf_len); +int recv_timesync_ans(const uint8_t buf[], uint8_t buf_len); void process_timesync_req(void *taskparameter); void store_time_sync_req(uint32_t t_millisec); -void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, timesource_t timesource); -#endif \ No newline at end of file +#endif diff --git a/platformio.ini b/platformio.ini index a4499620..3d1eda59 100644 --- a/platformio.ini +++ b/platformio.ini @@ -19,6 +19,7 @@ halfile = generic.h ;halfile = ttgov21new.h ;halfile = ttgofox.h ;halfile = ttgobeam.h +;halfile = ttgobeam10.h ;halfile = fipy.h ;halfile = lopy.h ;halfile = lopy4.h @@ -28,6 +29,8 @@ halfile = generic.h ;halfile = wemos32oled.h ;halfile = wemos32matrix.h ;halfile = octopus32.h +;halfile = tinypico.h +;halfile = tinypicomatrix.h [platformio] ; upload firmware to board with usb cable @@ -40,7 +43,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 = 1.7.841 +release_version = 1.8.34 ; 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 @@ -52,14 +55,13 @@ platform_espressif32 = espressif32@1.9.0 monitor_speed = 115200 upload_speed = 115200 lib_deps_lora = - ;MCCI LoRaWAN LMIC library@2.3.2 - https://github.com/mcci-catena/arduino-lmic.git + MCCI LoRaWAN LMIC library@>=3.0.99 lib_deps_display = U8g2@>=2.26.13 lib_deps_matrix_display = https://github.com/Seeed-Studio/Ultrathin_LED_Matrix.git lib_deps_rgbled = - SmartLeds@>=1.1.5 + SmartLeds@>=1.1.6 lib_deps_gps = 1655@>=1.0.2 ;TinyGPSPlus by Mikal Hart lib_deps_sensors = @@ -70,6 +72,8 @@ lib_deps_basic = 76@>=1.2.2 ;Timezone by Jack Christensen 274@>=2.3.3 ;RTC by Michael Miller SimpleButton + ;AXP202X_Library@^1.0.1 + https://github.com/lewisxhe/AXP202X_Library.git#8045ddf lib_deps_all = ${common.lib_deps_basic} ${common.lib_deps_lora} @@ -115,6 +119,4 @@ upload_protocol = esptool upload_protocol = esptool build_type = debug platform = https://github.com/platformio/platform-espressif32.git#develop -platform_packages = - ; use upstream Git version - framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git +platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git diff --git a/src/TTN/packed_decoder.js b/src/TTN/packed_decoder.js index 0999c07d..9ee6cf4f 100644 --- a/src/TTN/packed_decoder.js +++ b/src/TTN/packed_decoder.js @@ -37,7 +37,7 @@ function Decoder(bytes, port) { if (port === 3) { // device config data - return decode(bytes, [uint8, uint8, int16, uint8, uint8, uint8, uint8, bitmap1, bitmap2, version], ['lorasf', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags', 'payloadmask', 'version']); + return decode(bytes, [uint8, uint8, int16, uint8, uint8, uint8, uint8, bitmap1, bitmap2, version], ['loradr', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags', 'payloadmask', 'version']); } if (port === 4) { diff --git a/src/battery.cpp b/src/battery.cpp deleted file mode 100644 index 3af874d9..00000000 --- a/src/battery.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "globals.h" - -// Local logging tag -static const char TAG[] = __FILE__; - -#ifdef BAT_MEASURE_ADC -esp_adc_cal_characteristics_t *adc_characs = - (esp_adc_cal_characteristics_t *)calloc( - 1, sizeof(esp_adc_cal_characteristics_t)); - -static const adc1_channel_t adc_channel = BAT_MEASURE_ADC; -static const adc_atten_t atten = ADC_ATTEN_DB_11; -static const adc_unit_t unit = ADC_UNIT_1; -#endif - -void calibrate_voltage(void) { -#ifdef BAT_MEASURE_ADC - // configure ADC - ESP_ERROR_CHECK(adc1_config_width(ADC_WIDTH_BIT_12)); - ESP_ERROR_CHECK(adc1_config_channel_atten(adc_channel, atten)); - // calibrate ADC - esp_adc_cal_value_t val_type = esp_adc_cal_characterize( - unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_characs); - // show ADC characterization base - if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { - ESP_LOGI(TAG, - "ADC characterization based on Two Point values stored in eFuse"); - } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { - ESP_LOGI(TAG, - "ADC characterization based on reference voltage stored in eFuse"); - } else { - ESP_LOGI(TAG, "ADC characterization based on default reference voltage"); - } -#endif -} - -uint16_t read_voltage() { -#ifdef BAT_MEASURE_ADC - // multisample ADC - uint32_t adc_reading = 0; - for (int i = 0; i < NO_OF_SAMPLES; i++) { - adc_reading += adc1_get_raw(adc_channel); - } - adc_reading /= NO_OF_SAMPLES; - // Convert ADC reading to voltage in mV - uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_characs); -#ifdef BAT_VOLTAGE_DIVIDER - voltage *= BAT_VOLTAGE_DIVIDER; -#endif - -#ifdef BAT_MEASURE_EN // turn ext. power off - digitalWrite(EXT_POWER_SW, EXT_POWER_OFF); -#endif - - return (uint16_t)voltage; -#else - return 0; -#endif -} - -bool batt_sufficient() { -#ifdef BAT_MEASURE_ADC - uint16_t volts = read_voltage(); - return ((volts < 1000) || - (volts > OTA_MIN_BATT)); // no battery or battery sufficient -#else - return true; -#endif -} \ No newline at end of file diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 78298798..4a53bf89 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -143,7 +143,9 @@ int checkIaqSensorStatus(void) { // store current BME sensor data in struct void bme_storedata(bmeStatus_t *bme_store) { - if (I2C_MUTEX_LOCK()) { // block i2c bus access + + if ((cfg.payloadmask & MEMS_DATA) && + (I2C_MUTEX_LOCK())) { // block i2c bus access #ifdef HAS_BME680 if (iaqSensor.run()) { // if new data is available diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 1287d485..5b235f70 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -8,16 +8,21 @@ static const char TAG[] = "flash"; nvs_handle my_handle; esp_err_t err; +#define PAYLOADMASK \ + ((GPS_DATA | ALARM_DATA | MEMS_DATA | COUNT_DATA | SENSOR1_DATA | \ + SENSOR2_DATA | SENSOR3_DATA) & \ + ~BATT_DATA) + // populate cfg vars with factory settings void defaultConfig() { - cfg.lorasf = LORASFDEFAULT; // 7-12, initial lora sf, see pacounter.conf - cfg.txpower = 15; // 2-15, lora tx power - cfg.adrmode = 1; // 0=disabled, 1=enabled - cfg.screensaver = 0; // 0=disabled, 1=enabled - cfg.screenon = 1; // 0=disabled, 1=enabled - cfg.countermode = 0; // 0=cyclic, 1=cumulative, 2=cyclic confirmed - cfg.rssilimit = 0; // threshold for rssilimiter, negative value! - cfg.sendcycle = SENDCYCLE; // payload send cycle [seconds/2] + cfg.loradr = LORADRDEFAULT; // 0-15, lora datarate, see pacounter.conf + cfg.txpower = LORATXPOWDEFAULT; // 0-15, lora tx power + cfg.adrmode = 1; // 0=disabled, 1=enabled + cfg.screensaver = 0; // 0=disabled, 1=enabled + cfg.screenon = 1; // 0=disabled, 1=enabled + cfg.countermode = 0; // 0=cyclic, 1=cumulative, 2=cyclic confirmed + cfg.rssilimit = 0; // threshold for rssilimiter, negative value! + cfg.sendcycle = SENDCYCLE; // payload send cycle [seconds/2] cfg.wifichancycle = WIFI_CHANNEL_SWITCH_INTERVAL; // wifi channel switch cycle [seconds/100] cfg.blescantime = @@ -29,7 +34,7 @@ void defaultConfig() { cfg.rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) cfg.monitormode = 0; // 0=disabled, 1=enabled cfg.runmode = 0; // 0=normal, 1=update - cfg.payloadmask = 0xFF; // all payload switched on + cfg.payloadmask = PAYLOADMASK; // all payload switched on cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE] = { 0}; // init BSEC state for BME680 sensor @@ -92,9 +97,9 @@ void saveConfig() { strcmp(storedversion, cfg.version) != 0) nvs_set_str(my_handle, "version", cfg.version); - if (nvs_get_i8(my_handle, "lorasf", &flash8) != ESP_OK || - flash8 != cfg.lorasf) - nvs_set_i8(my_handle, "lorasf", cfg.lorasf); + if (nvs_get_i8(my_handle, "loradr", &flash8) != ESP_OK || + flash8 != cfg.loradr) + nvs_set_i8(my_handle, "loradr", cfg.loradr); if (nvs_get_i8(my_handle, "txpower", &flash8) != ESP_OK || flash8 != cfg.txpower) @@ -217,11 +222,11 @@ void loadConfig() { ESP_LOGI(TAG, "bsecstate = %d", cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE]); }; - if (nvs_get_i8(my_handle, "lorasf", &flash8) == ESP_OK) { - cfg.lorasf = flash8; - ESP_LOGI(TAG, "lorasf = %d", flash8); + if (nvs_get_i8(my_handle, "loradr", &flash8) == ESP_OK) { + cfg.loradr = flash8; + ESP_LOGI(TAG, "loradr = %d", flash8); } else { - ESP_LOGI(TAG, "lorasf set to default %d", cfg.lorasf); + ESP_LOGI(TAG, "loradr set to default %d", cfg.loradr); saveConfig(); } diff --git a/src/cyclic.cpp b/src/cyclic.cpp index d78d56c4..ef2ce200 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -30,6 +30,9 @@ void doHousekeeping() { #if (HAS_LORA) ESP_LOGD(TAG, "LMiCtask %d bytes left | Taskstate = %d", uxTaskGetStackHighWaterMark(lmicTask), eTaskGetState(lmicTask)); + ESP_LOGD(TAG, "Lorasendtask %d bytes left | Taskstate = %d", + uxTaskGetStackHighWaterMark(lorasendTask), + eTaskGetState(lorasendTask)); #endif #if (HAS_GPS) ESP_LOGD(TAG, "Gpsloop %d bytes left | Taskstate = %d", @@ -52,9 +55,15 @@ void doHousekeeping() { #endif // read battery voltage into global variable -#ifdef BAT_MEASURE_ADC +#if (defined BAT_MEASURE_ADC || defined HAS_PMU) batt_voltage = read_voltage(); - ESP_LOGI(TAG, "Voltage: %dmV", batt_voltage); + if (batt_voltage == 0xffff) + ESP_LOGI(TAG, "Battery: external power"); + else + ESP_LOGI(TAG, "Battery: %dmV", batt_voltage); +#ifdef HAS_PMU + AXP192_showstatus(); +#endif #endif // display BME680/280 sensor data diff --git a/src/display.cpp b/src/display.cpp index d6cb3d3e..b89f551d 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -10,15 +10,17 @@ Display-Mask (128 x 64 pixel): 0|PAX:aabbccddee 1|PAX:aabbccddee 2|B:a.bcV Sats:ab -3|BLTH:abcde SF:ab +3|BLTH:abcde SFab 4|WIFI:abcde ch:ab 5|RLIM:abcd abcdKB -6|xxxxxxxxxxxxxxxx 6|20:27:00* 27.Feb 7|yyyyyyyyyyyyyyab - -line 6: x = Text for LORA status OR time/date -line 7: y = Text for LMIC status; ab = payload queue + +line 6: * = char {L|G|R|?} indicates time source, + inverse = clock controller is active, + pulsed = pps input signal is active + +line 7: y = LMIC event message; ab = payload queue length */ @@ -30,19 +32,6 @@ line 7: y = Text for LMIC status; ab = payload queue HAS_DISPLAY u8x8(MY_OLED_RST, MY_OLED_SCL, MY_OLED_SDA); -// helper string for converting LoRa spread factor values -#if defined(CFG_eu868) -const char lora_datarate[] = {"1211100908077BFSNA"}; -#elif defined(CFG_us915) -const char lora_datarate[] = {"100908078CNA121110090807"}; -#elif defined(CFG_as923) -const char lora_datarate[] = {"1211100908077BFSNA"}; -#elif defined(CFG_au921) -const char lora_datarate[] = {"1211100908078CNA1211109C8C7C"}; -#elif defined(CFG_in866) -const char lora_datarate[] = {"121110090807FSNA"}; -#endif - // helper arry for converting month values to text const char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; @@ -188,9 +177,12 @@ void draw_page(time_t t, uint8_t page) { case 0: // update Battery status (line 2) -#ifdef BAT_MEASURE_ADC +#if (defined BAT_MEASURE_ADC || defined HAS_PMU) u8x8.setCursor(0, 2); - u8x8.printf("B:%.2fV", batt_voltage / 1000.0); + if (batt_voltage == 0xffff) + u8x8.printf("B:USB "); + else + u8x8.printf("B:%.2fV", batt_voltage / 1000.0); #endif // update GPS status (line 2) @@ -215,13 +207,11 @@ void draw_page(time_t t, uint8_t page) { #endif #if (HAS_LORA) - u8x8.setCursor(11, 3); - u8x8.printf("SF:"); - if (cfg.adrmode) // if ADR=on then display SF value inverse + u8x8.setCursor(12, 3); + if (!cfg.adrmode) // if ADR=off then display SF value inverse u8x8.setInverseFont(1); - u8x8.printf("%c%c", lora_datarate[LMIC.datarate * 2], - lora_datarate[LMIC.datarate * 2 + 1]); - if (cfg.adrmode) // switch off inverse if it was turned on + u8x8.printf("%-4s", getSfName(updr2rps(LMIC.datarate))); + if (!cfg.adrmode) // switch off inverse if it was turned on u8x8.setInverseFont(0); #endif // HAS_LORA @@ -254,17 +244,13 @@ void draw_page(time_t t, uint8_t page) { #endif // HAS_DCF77 || HAS_IF482 if (timeSource != _unsynced) u8x8.printf(" %2d.%3s", day(t), printmonth[month(t)]); -#else // update LoRa status display -#if (HAS_LORA) - u8x8.printf("%-16s", display_line6); -#endif #endif // TIME_SYNC_INTERVAL #if (HAS_LORA) // line 7: update LMiC event display u8x8.setCursor(0, 7); - u8x8.printf("%-14s", display_line7); + u8x8.printf("%-14s", lmic_event_msg); // update LoRa send queue display msgWaiting = uxQueueMessagesWaiting(LoraSendQueue); @@ -274,7 +260,6 @@ void draw_page(time_t t, uint8_t page) { u8x8.printf("%-2s", msgWaiting == SEND_QUEUE_SIZE ? "<>" : buff); } else u8x8.printf(" "); - #endif // HAS_LORA break; // page0 diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 262daf0a..eab3b3b3 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -90,13 +90,12 @@ time_t fetch_gpsTime(uint16_t *msec) { // poll NMEA $GPZDA sentence #ifdef GPS_SERIAL GPS_Serial.print(ZDA_Request); + // wait for gps NMEA answer + vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL)); #elif defined GPS_I2C Wire.print(ZDA_Request); #endif - // wait for gps NMEA answer - //vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL)); - // did we get a current time? if (gpstime.isUpdated() && gpstime.isValid()) { @@ -104,7 +103,8 @@ time_t fetch_gpsTime(uint16_t *msec) { String rawtime = gpstime.value(); uint32_t time_bcd = rawtime.toFloat() * 100; - uint32_t delay_ms = gpstime.age() + nmea_txDelay_ms + NMEA_COMPENSATION_FACTOR; + uint32_t delay_ms = + gpstime.age() + nmea_txDelay_ms + NMEA_COMPENSATION_FACTOR; uint8_t year = CalendarYrToTm(gps.date.year()); // year offset from 1970 in microTime.h @@ -138,7 +138,7 @@ void gps_loop(void *pvParameters) { while (1) { - if (cfg.payloadmask && GPS_DATA) { + if (cfg.payloadmask & GPS_DATA) { #ifdef GPS_SERIAL // feed GPS decoder with serial NMEA data from GPS device while (GPS_Serial.available()) { @@ -153,10 +153,10 @@ void gps_loop(void *pvParameters) { #endif } // if - // show NMEA data in verbose mode, useful for debugging GPS - ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: %u", - gps.passedChecksum(), gps.failedChecksum(), - gps.sentencesWithFix()); + // show NMEA data in verbose mode, useful for debugging GPS, bu tvery noisy + //ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: %u", + // gps.passedChecksum(), gps.failedChecksum(), + // gps.sentencesWithFix()); delay(2); // yield to CPU diff --git a/src/hal/eboxtube.h b/src/hal/eboxtube.h index 9278f0a6..34abe865 100644 --- a/src/hal/eboxtube.h +++ b/src/hal/eboxtube.h @@ -14,7 +14,7 @@ #define CFG_sx1276_radio 1 #define HAS_LED (22) // Green LED on board -#define HAS_RGB_LED (2) // WS2812B RGB LED on board +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_2) // WS2812B RGB LED on board #define HAS_BUTTON (0) // button "FLASH" on board #define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature diff --git a/src/hal/fipy.h b/src/hal/fipy.h index c59dff59..f904d716 100644 --- a/src/hal/fipy.h +++ b/src/hal/fipy.h @@ -13,7 +13,7 @@ #define CFG_sx1272_radio 1 #define HAS_LED NOT_A_PIN // FiPy has no on board LED, so we use RGB LED -#define HAS_RGB_LED GPIO_NUM_0 // WS2812B RGB LED on GPIO0 +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0 #define BOARD_HAS_PSRAM // use extra 4MB extern RAM // Pins for LORA chip SPI interface, reset line and interrupt lines diff --git a/src/hal/generic.h b/src/hal/generic.h index 1579221f..c606e5ba 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -51,7 +51,7 @@ #define HAS_LED (21) // on board LED #define HAS_BUTTON (39) // on board button -#define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0 +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0 // GPS settings #define HAS_GPS 1 // use on board GPS diff --git a/src/hal/heltecv2.h b/src/hal/heltecv2.h index 3f6bf743..adb74981 100644 --- a/src/hal/heltecv2.h +++ b/src/hal/heltecv2.h @@ -20,8 +20,13 @@ #define HAS_LED LED_BUILTIN // white LED on board #define HAS_BUTTON KEY_BUILTIN // button "PROG" on board +// caveat: activating ADC2 conflicts with Wifi in current arduino-esp32 +// see https://github.com/espressif/arduino-esp32/issues/3222 +// thus we must waiver of battery monitoring //#define BAT_MEASURE_ADC ADC2_GPIO13_CHANNEL // battery probe GPIO pin -//#define BAT_VOLTAGE_DIVIDER 4 // voltage divider 220k/100k on board +//#define BAT_MEASURE_ADC_UNIT 2 // ADC 2 +//#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 220k/100k on board + #define EXT_POWER_SW Vext // switches battery power, Vext control 0 = on / 1 = off #define EXT_POWER_ON 0 //#define EXT_POWER_OFF 1 diff --git a/src/hal/lolin32litelora.h b/src/hal/lolin32litelora.h index 79f9bb6b..11f3f156 100644 --- a/src/hal/lolin32litelora.h +++ b/src/hal/lolin32litelora.h @@ -17,7 +17,7 @@ //#define DISPLAY_FLIP 1 // uncomment this for rotated display #define HAS_LED 22 // ESP32 GPIO12 (pin22) On Board LED #define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW -#define HAS_RGB_LED 13 // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED #define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield #define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown diff --git a/src/hal/lolin32lora.h b/src/hal/lolin32lora.h index a6fa2f37..a79b0d84 100644 --- a/src/hal/lolin32lora.h +++ b/src/hal/lolin32lora.h @@ -18,7 +18,7 @@ #define HAS_LED NOT_A_PIN // Led os on same pin as Lora SS pin, to avoid problems, we don't use it #define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW // Anyway shield is on over the LoLin32 board, so we won't be able to see this LED -#define HAS_RGB_LED 13 // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED #define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield #define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown diff --git a/src/hal/lopy.h b/src/hal/lopy.h index 4bf50781..733eb603 100644 --- a/src/hal/lopy.h +++ b/src/hal/lopy.h @@ -12,7 +12,7 @@ #define HAS_LORA 1 // comment out if device shall not send data via LoRa #define CFG_sx1272_radio 1 #define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED -#define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0 +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0 (P2) // Note: Pins for LORA chip SPI interface come from board file pins_arduino.h diff --git a/src/hal/lopy4.h b/src/hal/lopy4.h index 321244e4..e0736229 100644 --- a/src/hal/lopy4.h +++ b/src/hal/lopy4.h @@ -20,8 +20,8 @@ //#define SPI_CS GPIO_NUM_36 #define CFG_sx1276_radio 1 -//#define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED -#define HAS_RGB_LED (0) // WS2812B RGB LED on GPIO0 (P2) +#define HAS_LED NOT_A_PIN // LoPy4 has no on board mono LED, we use on board RGB LED +#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_0) // WS2812B RGB LED on GPIO0 (P2) #define BOARD_HAS_PSRAM // use extra 4MB extern RAM // Note: Pins for LORA chip SPI interface come from board file pins_arduino.h diff --git a/src/hal/octopus32.h b/src/hal/octopus32.h index c2bf3735..d245a8c2 100644 --- a/src/hal/octopus32.h +++ b/src/hal/octopus32.h @@ -23,7 +23,7 @@ #define HAS_LED 13 // ESP32 GPIO12 (pin22) On Board LED //#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW -//#define HAS_RGB_LED 13 // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED +//#define HAS_RGB_LED SmartLed rgb_led(LED_WS2812, 1, GPIO_NUM_13) // ESP32 GPIO13 (pin13) On Board Shield WS2812B RGB LED //#define HAS_BUTTON 15 // ESP32 GPIO15 (pin15) Button is on the LoraNode32 shield //#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown diff --git a/src/hal/tinypico.h b/src/hal/tinypico.h new file mode 100644 index 00000000..415f793d --- /dev/null +++ b/src/hal/tinypico.h @@ -0,0 +1,24 @@ +// clang-format off +// upload_speed 921600 +// board esp32dev + + +#ifndef _TINYPICO_H +#define _TINYPICO_H + +#include + +// Hardware related definitions for crowdsupply tinypico board + +#define HAS_LED NOT_A_PIN // Green LED on board +#define HAS_RGB_LED Apa102 rgb_led(1, GPIO_NUM_12, GPIO_NUM_2) // APA102 RGB LED on board + +//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature +#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7 +#define BAT_VOLTAGE_DIVIDER 2.7625f // voltage divider 160k/442k on board +#define BOARD_HAS_PSRAM // use extra 4MB external RAM +#define LED_POWER_SW (13) // switches LED power +#define LED_POWER_ON 0 // switch on transistor for LED power +#define LED_POWER_OFF 1 + +#endif \ No newline at end of file diff --git a/src/hal/tinypicomatrix.h b/src/hal/tinypicomatrix.h new file mode 100644 index 00000000..73597446 --- /dev/null +++ b/src/hal/tinypicomatrix.h @@ -0,0 +1,46 @@ +// clang-format off +// upload_speed 921600 +// board esp32dev + + +#ifndef _TINYPICO_H +#define _TINYPICO_H + +#include + +// Hardware related definitions for crowdsupply tinypico board +// for operating a 96x16 shift register LED matrix display + +#define HAS_LED NOT_A_PIN // Green LED on board +#define HAS_RGB_LED Apa102 rgb_led(1, GPIO_NUM_12, GPIO_NUM_2) // APA102 RGB LED on board + +//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature +#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7 +#define BAT_VOLTAGE_DIVIDER 2.7625f // voltage divider 160k/442k on board +#define BOARD_HAS_PSRAM // use extra 4MB external RAM +#define LED_POWER_SW (13) // switches LED power +#define LED_POWER_ON 0 // switch on transistor for LED power +#define LED_POWER_OFF 1 + +// LED Matrix display settings +#define HAS_MATRIX_DISPLAY 1 // Uncomment to enable LED matrix display output +#define LED_MATRIX_WIDTH (32*2) // Width (cols) in pixels (LEDs) of your display, must be 32X +#define LED_MATRIX_HEIGHT (16*1) // Height (rows) in pixels (LEDs) of your display, must be 16X + +// Explanation of pin signals see https://learn.adafruit.com/32x16-32x32-rgb-led-matrix/new-wiring +#define MATRIX_DISPLAY_SCAN_US 500 // Matrix display scan rate in microseconds (1ms is about 'acceptable') +#define LED_MATRIX_LATCHPIN 32 // LAT (or STB = Strobe) +#define LED_MATRIX_CLOCKPIN 33 // CLK +#define LED_MATRIX_EN_74138 21 // EN (or OE) +#define LED_MATRIX_LA_74138 23 // LA (or A) +#define LED_MATRIX_LB_74138 19 // LB (or B) +#define LED_MATRIX_LC_74138 18 // LC (or C) +#define LED_MATRIX_LD_74138 5 // LD (or D) +#define LED_MATRIX_DATA_R1 22 // R1 (or R0) + +// CLK: The clock signal moves the data bits from pin R1 ("red") in the shift registers +// LAT: The latch signal enables LEDs according to the shift register's contents +// Line Selects: LA, LB, LC, LD select which rows of the display are currently lit (0 .. 15) +// OE: Output enable switches the LEDs on/off while transitioning from one row to the next + +#endif \ No newline at end of file diff --git a/src/hal/ttgobeam.h b/src/hal/ttgobeam.h index 41fd0ef0..f8be7ee0 100644 --- a/src/hal/ttgobeam.h +++ b/src/hal/ttgobeam.h @@ -17,7 +17,6 @@ #define HAS_LORA 1 // comment out if device shall not send data via LoRa #define CFG_sx1276_radio 1 // HPD13A LoRa SoC -#define BOARD_HAS_PSRAM // use extra 4MB external RAM #define HAS_BUTTON GPIO_NUM_39 // on board button (next to reset) #define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7 #define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board @@ -29,9 +28,9 @@ // 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 !! // display (if connected) #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h new file mode 100644 index 00000000..fa0b3ffa --- /dev/null +++ b/src/hal/ttgobeam10.h @@ -0,0 +1,74 @@ +// clang-format off +// upload_speed 921600 +// board ttgo-t-beam + +#ifndef _TTGOBEAM_H +#define _TTGOBEAM_H + +#include + +/* +Hardware related definitions for TTGO T-Beam board +(only) for newer T-Beam version T22_V10 +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, long press -> set device off +User, short press -> flip display page +User, long press -> send LORA message +Reset -> reset device +*/ + +//#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C +#define MY_OLED_SDA SDA +#define MY_OLED_SCL SCL +#define MY_OLED_RST U8X8_PIN_NONE +//#define DISPLAY_FLIP 1 // use if display is rotated + +#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_PMU 1 // AXP192 power management chip +#define PMU_INT GPIO_NUM_35 // AXP192 interrupt + +#define HAS_LED NOT_A_PIN + +// 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 +#define GPS_INT GPIO_NUM_37 // 30ns accurary timepulse generated by NEO 6M Pin #3 + +// 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 !! + +// user defined sensors (if connected) +//#define HAS_SENSORS 1 // comment out if device has user defined sensors + +//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature + +#endif + +/* + +// T-Beam V10 has on board power management by AXP192 PMU chip: +// +// DCDC1 0.7-3.5V @ 1200mA -> OLED +// DCDC3 0.7-3.5V @ 700mA -> ESP32 (keep this on!) +// LDO1 30mA -> GPS Backup +// LDO2 200mA -> LORA +// LDO3 200mA -> GPS + +// Wiring for I2C OLED display: +// +// Signal Header OLED +// 3V3 7 VCC +// GND 8 GND +// IO22(SCL) 9 SCL +// IO21(SDA) 10 SDA +// + +*/ diff --git a/src/hal/wemos32matrix.h b/src/hal/wemos32matrix.h index 66935f00..b69ac074 100644 --- a/src/hal/wemos32matrix.h +++ b/src/hal/wemos32matrix.h @@ -11,8 +11,8 @@ // LED Matrix display settings #define HAS_MATRIX_DISPLAY 1 // Uncomment to enable LED matrix display output -#define LED_MATRIX_WIDTH 64 // Width in pixels (LEDs) of your display -#define LED_MATRIX_HEIGHT 16 // Height in pixels (LEDs ) of your display +#define LED_MATRIX_WIDTH (32*2) // Width (cols) in pixels (LEDs) of your display, must be 32X +#define LED_MATRIX_HEIGHT (16*1) // Height (rows) in pixels (LEDs) of your display, must be 16X // Pin numbers work fine for Wemos Lolin32 board (all used pins are on 1 side of the board) // Explanation of pin signals see https://learn.adafruit.com/32x16-32x32-rgb-led-matrix/new-wiring diff --git a/src/i2cscan.cpp b/src/i2cscan.cpp new file mode 100644 index 00000000..c80c2af7 --- /dev/null +++ b/src/i2cscan.cpp @@ -0,0 +1,66 @@ +// Basic config +#include "globals.h" +#include "i2cscan.h" + +// Local logging tag +static const char TAG[] = __FILE__; + +int i2c_scan(void) { + + int i2c_ret, addr; + int devices = 0; + + ESP_LOGI(TAG, "Starting I2C bus scan..."); + + // block i2c bus access + if (I2C_MUTEX_LOCK()) { + + for (addr = 8; addr <= 119; addr++) { + + // scan i2c bus with no more to 100KHz + Wire.beginTransmission(addr); + Wire.write(addr); + i2c_ret = Wire.endTransmission(); + + if (i2c_ret == 0) { + devices++; + + switch (addr) { + + case SSD1306_PRIMARY_ADDRESS: + case SSD1306_SECONDARY_ADDRESS: + ESP_LOGI(TAG, "0x%X: SSD1306 Display controller", addr); + break; + + case BME_PRIMARY_ADDRESS: + case BME_SECONDARY_ADDRESS: + ESP_LOGI(TAG, "0x%X: Bosch BME MEMS", addr); + break; + + case AXP192_PRIMARY_ADDRESS: + ESP_LOGI(TAG, "0x%X: AXP192 power management", addr); + break; + + case QUECTEL_GPS_PRIMARY_ADDRESS: + ESP_LOGI(TAG, "0x%X: Quectel GPS", addr); + break; + + case MCP_24AA02E64_PRIMARY_ADDRESS: + ESP_LOGI(TAG, "0x%X: 24AA02E64 serial EEPROM", addr); + break; + + default: + ESP_LOGI(TAG, "0x%X: Unknown device", addr); + break; + } + } // switch + } // for loop + + ESP_LOGI(TAG, "I2C scan done, %u devices found.", devices); + + I2C_MUTEX_UNLOCK(); // release i2c bus access + } else + ESP_LOGE(TAG, "I2c bus busy - scan error"); + + return devices; +} \ No newline at end of file diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index cb5de1ec..8d1be880 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -63,9 +63,15 @@ void irqHandler(void *pvParameters) { } #endif +// do we have a power event? +#if (HAS_PMU) + if (InterruptStatus & PMU_IRQ) + power_event_IRQ(); +#endif + // is time to send the payload? if (InterruptStatus & SENDCYCLE_IRQ) - sendCounter(); + sendData(); } } @@ -106,6 +112,18 @@ void IRAM_ATTR ButtonIRQ() { } #endif +#ifdef HAS_PMU +void IRAM_ATTR PMUIRQ() { + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + + xTaskNotifyFromISR(irqHandlerTask, PMU_IRQ, eSetBits, + &xHigherPriorityTaskWoken); + + if (xHigherPriorityTaskWoken) + portYIELD_FROM_ISR(); +} +#endif + void mask_user_IRQ() { xTaskNotify(irqHandlerTask, MASK_IRQ, eSetBits); } void unmask_user_IRQ() { xTaskNotify(irqHandlerTask, UNMASK_IRQ, eSetBits); } \ No newline at end of file diff --git a/src/led.cpp b/src/led.cpp index 624fb7d9..d7925b4e 100644 --- a/src/led.cpp +++ b/src/led.cpp @@ -14,7 +14,7 @@ unsigned long LEDBlinkStarted = 0; // When (in millis() led blink started) #ifdef HAS_RGB_LED // RGB Led instance -SmartLed rgb_led(LED_WS2812, 1, HAS_RGB_LED); +HAS_RGB_LED; float rgb_CalcColor(float p, float q, float t) { if (t < 0.0f) diff --git a/src/ledmatrixdisplay.cpp b/src/ledmatrixdisplay.cpp index 3cfe31f2..881f57fe 100644 --- a/src/ledmatrixdisplay.cpp +++ b/src/ledmatrixdisplay.cpp @@ -2,13 +2,14 @@ #include "globals.h" -#define NUMCHARS 5 #define MATRIX_DISPLAY_PAGES (2) // number of display pages +#define LINE_DIAGRAM_DIVIDER (2) // scales pax numbers to led rows // local Tag for logging static const char TAG[] = __FILE__; uint8_t MatrixDisplayIsOn = 0; +static uint8_t displaybuf[LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT / 8] = {0}; static unsigned long ulLastNumMacs = 0; static time_t ulLastTime = myTZ.toLocal(now()); @@ -16,9 +17,6 @@ LEDMatrix matrix(LED_MATRIX_LA_74138, LED_MATRIX_LB_74138, LED_MATRIX_LC_74138, LED_MATRIX_LD_74138, LED_MATRIX_EN_74138, LED_MATRIX_DATA_R1, LED_MATRIX_LATCHPIN, LED_MATRIX_CLOCKPIN); -// Display Buffer 128 = 64 * 16 / 8 -uint8_t displaybuf[LED_MATRIX_WIDTH * LED_MATRIX_HEIGHT / NUMCHARS]; - // --- SELECT YOUR FONT HERE --- const FONT_INFO *ActiveFontInfo = &digital7_18ptFontInfo; // const FONT_INFO *ActiveFontInfo = &arialNarrow_17ptFontInfo; @@ -31,14 +29,21 @@ const FONT_CHAR_INFO *ActiveFontCharInfo = ActiveFontInfo->Descriptors; void init_matrix_display(bool reverse) { ESP_LOGI(TAG, "Initializing LED Matrix display"); matrix.begin(displaybuf, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT); + + if (MatrixDisplayIsOn) + matrix.on(); + else + matrix.off(); + if (reverse) matrix.reverse(); matrix.clear(); - DrawNumber(String("0")); + matrix.drawPoint(0, LED_MATRIX_HEIGHT - 1, 1); } // init_display void refreshTheMatrixDisplay(bool nextPage) { - static uint8_t DisplayPage = 0; + static uint8_t DisplayPage = 0, col = 0, row = 0; + uint8_t level; char buff[16]; // if Matrixdisplay is switched off we don't refresh it to relax cpu @@ -48,25 +53,60 @@ void refreshTheMatrixDisplay(bool nextPage) { // set display on/off according to current device configuration if (MatrixDisplayIsOn != cfg.screenon) { MatrixDisplayIsOn = cfg.screenon; + if (MatrixDisplayIsOn) + matrix.on(); + else + matrix.off(); } if (nextPage) { DisplayPage = (DisplayPage >= MATRIX_DISPLAY_PAGES - 1) ? 0 : (DisplayPage + 1); matrix.clear(); + col = 0; } switch (DisplayPage % MATRIX_DISPLAY_PAGES) { - // page 0: pax - // page 1: time + // page 0: number of current pax OR footfall line diagram + // page 1: time of day case 0: - if (ulLastNumMacs != macs.size()) { - ulLastNumMacs = macs.size(); - matrix.clear(); - DrawNumber(String(ulLastNumMacs)); + if (cfg.countermode == 1) + + { // cumulative counter mode -> display total number of pax + if (ulLastNumMacs != macs.size()) { + ulLastNumMacs = macs.size(); + matrix.clear(); + DrawNumber(String(ulLastNumMacs)); + } + } + + else { // cyclic counter mode -> plot a line diagram + + if (ulLastNumMacs != macs.size()) { + + // next count cycle? + if (macs.size() == 0) { + + // matrix full? then scroll left 1 dot, else increment column + if (col < (LED_MATRIX_WIDTH - 1)) + col++; + else + ScrollLeft(displaybuf, LED_MATRIX_WIDTH, LED_MATRIX_HEIGHT); + + } else + matrix.drawPoint(col, row, 0); // clear current dot + + // scale and set new dot + ulLastNumMacs = macs.size(); + level = ulLastNumMacs / LINE_DIAGRAM_DIVIDER; + row = level <= LED_MATRIX_HEIGHT + ? LED_MATRIX_HEIGHT - 1 - level % LED_MATRIX_HEIGHT + : 0; + matrix.drawPoint(col, row, 1); + } } break; @@ -164,4 +204,18 @@ uint8_t GetCharWidth(char cChar) { return CharDescriptor.width; } +void ScrollLeft(uint8_t *buf, const uint16_t cols, const uint16_t rows) { + uint32_t i, k, idx; + const uint32_t x = cols / 8; + + for (k = 0; k < rows; k++) { + // scroll a line with x bytes one dot to the left + for (i = 0; i < x - 1; ++i) { + idx = i + k * x; + buf[idx] = (buf[idx] << 1) | ((buf[idx + 1] >> 7) & 1); + } + buf[idx + 1] <<= 1; + } +} + #endif // HAS_MATRIX_DISPLAY \ No newline at end of file diff --git a/src/lmic_config.h b/src/lmic_config.h index 9e90a439..788a07e1 100644 --- a/src/lmic_config.h +++ b/src/lmic_config.h @@ -2,40 +2,33 @@ // COUNTRY SETTINGS // --> please check with you local regulations for ISM band frequency use! -// -// CFG_eu868 EU 863-870 MHz -// CFG_us915 US 902-928 MHz -// CFG_au921 Australia 915-928 MHz -// CFG_as923 Asia 923 MHz -// CFG_in866 India 865-867 MHz -#define CFG_eu868 1 -//#define CFG_us915 1 -//#define CFG_in866 1 -//#define CFG_au921 1 -//#define CFG_as923 1 -//#define LMIC_COUNTRY_CODE LMIC_COUNTRY_CODE_JP /* for as923-JP */ +#define CFG_eu868 1 // Europe (high band) +//#define CFG_eu433 1 // Europe (low band) +//#define CFG_us915 1 // USA, Canada and South America +//#define CFG_in866 1 // India +//#define CFG_au921 1 // Australia +//#define CFG_as923 1 // Asia +//#define CFG_cn783 1 // China (high band) +//#define CFG_cn490 1 // China (low band) +//#define CFG_kr920 1 // Korea // LMIC LORAWAN STACK SETTINGS // --> adapt to your device only if necessary -//#define LMIC_USE_INTERRUPTS 1 +// use interrupts only if LORA_IRQ and LORA_DIO are connected to interrupt +// capable GPIO pins on your board, if not disable interrupts +#define LMIC_USE_INTERRUPTS 1 -//time sync via LoRaWAN network, is not yet supported by TTN (LoRaWAN spec v1.0.3) -//#define LMIC_ENABLE_DeviceTimeReq 1 - -// 16 μs per tick -// LMIC requires ticks to be 15.5μs - 100 μs long -#define US_PER_OSTICK_EXPONENT 4 -#define US_PER_OSTICK (1 << US_PER_OSTICK_EXPONENT) -#define OSTICKS_PER_SEC (1000000 / US_PER_OSTICK) +// time sync via LoRaWAN network, note: not supported by TTNv2 +// #define LMIC_ENABLE_DeviceTimeReq 1 // 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) this value if you are -// limited on battery. +// limited on battery. // ATTN: VALUES > 7 WILL CAUSE RECEPTION AND JOIN PROBLEMS WITH HIGH SF RATES -#define CLOCK_ERROR_PROCENTAGE 5 +//#define CLOCK_ERROR_PROCENTAGE 7 // Set this to 1 to enable some basic debug output (using printf) about // RF settings used during transmission and reception. Set to 2 to @@ -49,6 +42,11 @@ // current implementation only works on AVR, though. //#define LMIC_PRINTF_TO Serial +// Change the SPI clock speed if you encounter errors +// communicating with the radio. +// The standard range is 125kHz-8MHz, but some boards can go faster. +//#define LMIC_SPI_FREQ 1E6 + // Any runtime assertion failures are printed to this serial port (or // any other Print object). If this is unset, any failures just silently // halt execution. @@ -90,7 +88,7 @@ // implementation is optimized for speed on 32-bit processors using // fairly big lookup tables, but it takes up big amounts of flash on the // AVR architecture. -//#define USE_ORIGINAL_AES +#define USE_ORIGINAL_AES // // This selects the AES implementation written by Ideetroon for their // own LoRaWAN library. It also uses lookup tables, but smaller @@ -98,4 +96,4 @@ // also about twice as slow as the original). // #define USE_IDEETRON_AES // -#define USE_MBEDTLS_AES +//#define USE_MBEDTLS_AES diff --git a/src/lorawan.cpp b/src/lorawan.cpp index c4ef37e3..e4e4ae58 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -18,9 +18,31 @@ static const char TAG[] = "lora"; #endif #endif -osjob_t sendjob; QueueHandle_t LoraSendQueue; -TaskHandle_t lmicTask = NULL; +TaskHandle_t lmicTask = NULL, lorasendTask = NULL; + +// table of LORAWAN MAC messages sent by the network to the device +// format: opcode, cmdname (max 19 chars), #bytes params +// source: LoRaWAN 1.1 Specification (October 11, 2017) +static const mac_t MACdn_table[] = { + {0x01, "ResetConf", 1}, {0x02, "LinkCheckAns", 2}, + {0x03, "LinkADRReq", 4}, {0x04, "DutyCycleReq", 1}, + {0x05, "RXParamSetupReq", 4}, {0x06, "DevStatusReq", 0}, + {0x07, "NewChannelReq", 5}, {0x08, "RxTimingSetupReq", 1}, + {0x09, "TxParamSetupReq", 1}, {0x0A, "DlChannelReq", 4}, + {0x0B, "RekeyConf", 1}, {0x0C, "ADRParamSetupReq", 1}, + {0x0D, "DeviceTimeAns", 5}, {0x0E, "ForceRejoinReq", 2}, + {0x0F, "RejoinParamSetupReq", 1}}; + +// table of LORAWAN MAC messages sent by the device to the network +static const mac_t MACup_table[] = { + {0x01, "ResetInd", 1}, {0x02, "LinkCheckReq", 0}, + {0x03, "LinkADRAns", 1}, {0x04, "DutyCycleAns", 0}, + {0x05, "RXParamSetupAns", 1}, {0x06, "DevStatusAns", 2}, + {0x07, "NewChannelAns", 1}, {0x08, "RxTimingSetupAns", 0}, + {0x09, "TxParamSetupAns", 0}, {0x0A, "DlChannelAns", 1}, + {0x0B, "RekeyInd", 1}, {0x0C, "ADRParamSetupAns", 0}, + {0x0D, "DeviceTimeReq", 0}, {0x0F, "RejoinParamSetupAns", 1}}; class MyHalConfig_t : public Arduino_LMIC::HalConfiguration_t { @@ -135,7 +157,7 @@ void get_hard_deveui(uint8_t *pdeveui) { uint8_t i2c_ret; // Init this just in case, no more to 100KHz - Wire.begin(MY_OLED_SDA, MY_OLED_SCL, 100000); + Wire.begin(SDA, SCL, 100000); Wire.beginTransmission(MCP_24AA02E64_I2C_ADDRESS); Wire.write(MCP_24AA02E64_MAC_ADDRESS); i2c_ret = Wire.endTransmission(); @@ -209,19 +231,17 @@ void onEvent(ev_t ev) { case EV_JOINED: strcpy_P(buff, PSTR("JOINED")); - sprintf(display_line6, " "); // clear previous lmic status // set data rate adaptation according to saved setting LMIC_setAdrMode(cfg.adrmode); - // set cyclic lmic link check to off if no ADR because is not supported by - // ttn (but enabled by lmic after join) - LMIC_setLinkCheckMode(cfg.adrmode); - // Set data rate and transmit power (note: txpower seems to be ignored by - // the library) - switch_lora(cfg.lorasf, cfg.txpower); - // kickoff first send job - os_setCallback(&sendjob, lora_send); - // show effective LoRa parameters after join + // set data rate and transmit power to defaults only if we have no ADR + if (!cfg.adrmode) + LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower); + // show current devaddr ESP_LOGI(TAG, "DEVaddr=%08X", LMIC.devaddr); + ESP_LOGI(TAG, "Radio parameters %s / %s / %s", + getSfName(updr2rps(LMIC.datarate)), + getBwName(updr2rps(LMIC.datarate)), + getCrName(updr2rps(LMIC.datarate))); break; case EV_RFU1: @@ -237,46 +257,7 @@ void onEvent(ev_t ev) { break; case EV_TXCOMPLETE: - -#if (TIME_SYNC_LORASERVER) - // if last packet sent was a timesync request, store TX timestamp - if (LMIC.pendTxPort == TIMEPORT) - store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds -#endif - - strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK") - : PSTR("TX COMPLETE")); - sprintf(display_line6, " "); // clear previous lmic status - - if (LMIC.dataLen) { // did we receive payload data -> display info - ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d", - LMIC.dataLen, LMIC.rssi, LMIC.snr / 4); - sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4); - - if (LMIC.txrxFlags & TXRX_PORT) { // FPort -> use to switch - - switch (LMIC.frame[LMIC.dataBeg - 1]) { - - case RCMDPORT: // opcode -> call rcommand interpreter - rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - break; - - default: - -#if (TIME_SYNC_LORASERVER) - // timesync answer -> call timesync processor - if (LMIC.frame[LMIC.dataBeg - 1] == TIMEPORT) { - recv_timesync_ans(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - break; - } -#endif - // unknown port -> display info - ESP_LOGI(TAG, "Received data on unsupported port #%d", - LMIC.frame[LMIC.dataBeg - 1]); - break; - } - } - } + strcpy_P(buff, PSTR("TX COMPLETE")); break; case EV_LOST_TSYNC: @@ -306,13 +287,7 @@ void onEvent(ev_t ev) { case EV_TXSTART: if (!(LMIC.opmode & OP_JOINING)) { -#if (TIME_SYNC_LORASERVER) - // if last packet sent was a timesync request, store TX time - if (LMIC.pendTxPort == TIMEPORT) - strcpy_P(buff, PSTR("TX TIMESYNC")); - else -#endif - strcpy_P(buff, PSTR("TX START")); + strcpy_P(buff, PSTR("TX START")); } break; @@ -336,80 +311,58 @@ void onEvent(ev_t ev) { // Log & Display if asked if (*buff) { ESP_LOGI(TAG, "%s", buff); - sprintf(display_line7, buff); + sprintf(lmic_event_msg, buff); } } -// helper function to assign LoRa datarates to numeric spreadfactor values -void switch_lora(uint8_t sf, uint8_t tx) { - if (tx > 20) - return; - cfg.txpower = tx; - switch (sf) { - case 7: - LMIC_setDrTxpow(DR_SF7, tx); - cfg.lorasf = sf; - break; - case 8: - LMIC_setDrTxpow(DR_SF8, tx); - cfg.lorasf = sf; - break; - case 9: - LMIC_setDrTxpow(DR_SF9, tx); - cfg.lorasf = sf; - break; - case 10: - LMIC_setDrTxpow(DR_SF10, tx); - cfg.lorasf = sf; - break; - case 11: -#if defined(CFG_us915) - LMIC_setDrTxpow(DR_SF11CR, tx); - cfg.lorasf = sf; - break; -#else - LMIC_setDrTxpow(DR_SF11, tx); - cfg.lorasf = sf; - break; -#endif - case 12: -#if defined(CFG_us915) - LMIC_setDrTxpow(DR_SF12CR, tx); - cfg.lorasf = sf; - break; -#else - LMIC_setDrTxpow(DR_SF12, tx); - cfg.lorasf = sf; - break; -#endif - default: - break; - } -} +// LMIC send task +void lora_send(void *pvParameters) { + configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check -void lora_send(osjob_t *job) { MessageBuffer_t SendBuffer; - // Check if there is a pending TX/RX job running, if yes don't eat data - // since it cannot be sent right now - if ((LMIC.opmode & (OP_JOINING | OP_REJOIN | OP_TXDATA | OP_POLL)) != 0) { - // waiting for LoRa getting ready - } else { - if (xQueueReceive(LoraSendQueue, &SendBuffer, (TickType_t)0) == pdTRUE) { - // SendBuffer now filled with next payload from queue - if (!LMIC_setTxData2(SendBuffer.MessagePort, SendBuffer.Message, - SendBuffer.MessageSize, (cfg.countermode & 0x02))) { - ESP_LOGI(TAG, "%d byte(s) sent to LoRa", SendBuffer.MessageSize); - } else { - ESP_LOGE(TAG, "could not send %d byte(s) to LoRa", - SendBuffer.MessageSize); - } - // sprintf(display_line7, "PACKET QUEUED"); + + while (1) { + + // postpone until we are joined if we are not + while (!LMIC.devaddr) { + vTaskDelay(pdMS_TO_TICKS(500)); } + + // fetch next or wait for payload to send from queue + if (xQueueReceive(LoraSendQueue, &SendBuffer, portMAX_DELAY) != pdTRUE) { + ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); + continue; + } + + // attempt to transmit payload + else { + + switch (LMIC_sendWithCallback_strict( + SendBuffer.MessagePort, SendBuffer.Message, SendBuffer.MessageSize, + (cfg.countermode & 0x02), myTxCallback, NULL)) { + + case LMIC_ERROR_SUCCESS: + 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 + } + delay(2); // yield to CPU } - // reschedule job every 0,5 - 1 sec. including a bit of random to prevent - // systematic collisions - os_setTimedCallback(job, os_getTime() + 500 + ms2osticks(random(500)), - lora_send); } esp_err_t lora_stack_init() { @@ -422,13 +375,13 @@ esp_err_t lora_stack_init() { ESP_LOGI(TAG, "LORA send queue created, size %d Bytes", SEND_QUEUE_SIZE * sizeof(MessageBuffer_t)); - // starting lorawan stack + // start lorawan stack ESP_LOGI(TAG, "Starting LMIC..."); xTaskCreatePinnedToCore(lmictask, // task function "lmictask", // name of task 4096, // stack size of task (void *)1, // parameter of the task - 2, // priority of the task + 5, // priority of the task &lmicTask, // task handle 1); // CPU core @@ -436,18 +389,31 @@ esp_err_t lora_stack_init() { ESP_LOGI(TAG, "Already joined"); } - return ESP_OK; // continue main program + // start lmic send task + xTaskCreatePinnedToCore(lora_send, // task function + "lorasendtask", // name of task + 3072, // stack size of task + (void *)1, // parameter of the task + 1, // priority of the task + &lorasendTask, // task handle + 1); // CPU core + + return ESP_OK; } -void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { +void lora_enqueuedata(MessageBuffer_t *message) { // enqueue message in LORA send queue - BaseType_t ret; + BaseType_t ret = pdFALSE; 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(LoraSendQueue) == 0) + // 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 = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0); break; @@ -519,17 +485,17 @@ void lmictask(void *pvParameters) { os_init(); // initialize lmic run-time environment LMIC_reset(); // initialize lmic MAC LMIC_setLinkCheckMode(0); - // 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. - LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100); - // Set the data rate to Spreading Factor 7. This is the fastest supported - // rate for 125 kHz channels, and it minimizes air time and battery power. - // Set the transmission power to 14 dBi (25 mW). - LMIC_setDrTxpow(DR_SF7, 14); -#if defined(CFG_US915) || defined(CFG_au921) +// 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(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100); +#endif + +//#if defined(CFG_US915) || defined(CFG_au921) +#if CFG_LMIC_US_like // in the US, with TTN, it saves join time if we start on subband 1 // (channels 8-15). This will get overridden after the join by parameters // from the network. If working with other networks or in other regions, @@ -537,10 +503,161 @@ void lmictask(void *pvParameters) { LMIC_selectSubBand(1); #endif + // Set the data rate to Spreading Factor 7. This is the fastest supported + // rate for 125 kHz channels, and it minimizes air time and battery power. + // Set the transmission power to 14 dBi (25 mW). + LMIC_setDrTxpow(DR_SF7, 14); + + // register a callback for downlink messages. We aren't trying to write + // reentrant code, so pUserData is NULL. + LMIC_registerRxMessageCb(myRxCallback, NULL); + while (1) { os_runloop_once(); // execute lmic scheduled jobs and events delay(2); // yield to CPU } } // lmictask +// receive message handler +void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, + size_t nMsg) { + + // display type of received data + if (nMsg) + ESP_LOGI(TAG, "Received %u byte(s) of payload on port %u", nMsg, port); + else if (port) + ESP_LOGI(TAG, "Received empty message on port %u", port); + + // list MAC messages, if any + uint8_t nMac = pMsg - &LMIC.frame[0]; + if (port != MACPORT) + --nMac; + if (nMac) { + ESP_LOGI(TAG, "%u byte(s) downlink MAC commands", nMac); + // NOT WORKING YET + // whe need to unwrap the MAC command from LMIC.frame here + // mac_decode(LMIC.frame, nMac, MACdn_table, sizeof(MACdn_table) / + // sizeof(MACdn_table[0])); + } + + if (LMIC.pendMacLen) { + ESP_LOGI(TAG, "%u byte(s) uplink MAC commands", LMIC.pendMacLen); + mac_decode(LMIC.pendMacData, LMIC.pendMacLen, MACup_table, + sizeof(MACup_table) / sizeof(MACup_table[0])); + } + + switch (port) { + + // ignore mac messages + case MACPORT: + break; + + // rcommand received -> call interpreter + case RCMDPORT: + rcommand(pMsg, nMsg); + break; + + default: + +#if (TIME_SYNC_LORASERVER) + // valid timesync answer -> call timesync processor + if ((port >= TIMEANSWERPORT_MIN) && (port <= TIMEANSWERPORT_MAX)) { + recv_timesync_ans(port, pMsg, nMsg); + break; + } +#endif + + // unknown port -> display info + ESP_LOGI(TAG, "Received data on unsupported port %u", port); + break; + } // switch +} + +// transmit complete message handler +void myTxCallback(void *pUserData, int fSuccess) { + +#if (TIME_SYNC_LORASERVER) + // if last packet sent was a timesync request, store TX timestamp + if (LMIC.pendTxPort == TIMEPORT) + store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds +#endif +} + +// decode LORAWAN MAC message +void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, const mac_t table[], + const uint8_t tablesize) { + + if (!cmdlen) + return; + + uint8_t foundcmd[cmdlen], cursor = 0; + + while (cursor < cmdlen) { + + int i = tablesize; // number of commands in table + + while (i--) { + if (cmd[cursor] == table[i].opcode) { // lookup command in opcode table + cursor++; // strip 1 byte opcode + if ((cursor + table[i].params) <= cmdlen) { + memmove(foundcmd, cmd + cursor, + table[i].params); // strip opcode from cmd array + cursor += table[i].params; + ESP_LOGD(TAG, "MAC command %s", table[i].cmdname); + } else + ESP_LOGD(TAG, "MAC command 0x%02X with missing parameter(s)", + table[i].opcode); + break; // command found -> exit table lookup loop + } // end of command validation + } // end of command table lookup loop + if (i < 0) { // command not found -> skip it + ESP_LOGD(TAG, "Unknown MAC command 0x%02X", cmd[cursor]); + cursor++; + } + } // command parsing loop + +} // mac_decode() + +uint8_t getBattLevel() { + /* + return values: + MCMD_DEVS_EXT_POWER = 0x00, // external power supply + MCMD_DEVS_BATT_MIN = 0x01, // min battery value + MCMD_DEVS_BATT_MAX = 0xFE, // max battery value + MCMD_DEVS_BATT_NOINFO = 0xFF, // unknown battery level + */ +#if (defined HAS_PMU || defined BAT_MEASURE_ADC) + uint16_t voltage = read_voltage(); + + switch (voltage) { + case 0: + return MCMD_DEVS_BATT_NOINFO; + case 0xffff: + return MCMD_DEVS_EXT_POWER; + default: + return (voltage > OTA_MIN_BATT ? MCMD_DEVS_BATT_MAX : MCMD_DEVS_BATT_MIN); + } +#else // we don't have any info on battery level + return MCMD_DEVS_BATT_NOINFO; +#endif +} // getBattLevel() + +// u1_t os_getBattLevel(void) { return getBattLevel(); }; + +const char *getSfName(rps_t rps) { + const char *const t[] = {"FSK", "SF7", "SF8", "SF9", + "SF10", "SF11", "SF12", "SF?"}; + return t[getSf(rps)]; +} + +const char *getBwName(rps_t rps) { + const char *const t[] = {"BW125", "BW250", "BW500", "BW?"}; + return t[getBw(rps)]; +} + +const char *getCrName(rps_t rps) { + const char *const t[] = {"CR 4/5", "CR 4/6", "CR 4/7", "CR 4/8"}; + return t[getCr(rps)]; +} + #endif // HAS_LORA diff --git a/src/main.cpp b/src/main.cpp index a8e50ea5..73a52925 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -31,12 +31,12 @@ ledloop 0 3 blinks LEDs spiloop 0 2 reads/writes data on spi interface IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer +lmictask 1 5 MCCI LMiC LORAWAN stack clockloop 1 4 generates realtime telegrams for external clock timesync_req 1 3 processes realtime time sync requests -lmictask 1 2 MCCI LMiC LORAWAN stack irqhandler 1 1 display, timesync, gps, etc. triggered by timers gpsloop 1 1 reads data from GPS via serial or i2c -looptask 1 1 arduino loop (unused) +lorasendtask 1 1 feed data from lora sendqueue to lmcic IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator Low priority numbers denote low priority tasks. @@ -58,6 +58,7 @@ fired by hardware DisplayIRQ -> esp32 timer 0 -> irqHandlerTask (Core 1) CLOCKIRQ -> esp32 timer 1 -> ClockTask (Core 1) ButtonIRQ -> external gpio -> irqHandlerTask (Core 1) +PMUIRQ -> PMU chip gpio -> irqHandlerTask (Core 1) fired by software (Ticker.h) TIMESYNC_IRQ -> timeSync() -> irqHandlerTask (Core 1) @@ -75,9 +76,9 @@ triggers pps 1 sec impulse // Basic Config #include "main.h" -configData_t cfg; // struct holds current device configuration -char display_line6[16], display_line7[16]; // display buffers -uint8_t volatile channel = 0; // channel rotation counter +configData_t cfg; // struct holds current device configuration +char lmic_event_msg[16]; // display buffer for LMIC event message +uint8_t volatile channel = 0; // channel rotation counter uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0, batt_voltage = 0; // globals for display @@ -169,24 +170,45 @@ void setup() { ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion()); #endif +// open i2c bus +#ifdef HAS_DISPLAY + Wire.begin(MY_OLED_SDA, MY_OLED_SCL, 100000); +#else + Wire.begin(SDA, SCL, 100000); +#endif + +// setup power on boards with power management logic +#ifdef EXT_POWER_SW + pinMode(EXT_POWER_SW, OUTPUT); + digitalWrite(EXT_POWER_SW, EXT_POWER_ON); + strcat_P(features, " VEXT"); +#endif +#ifdef HAS_PMU + AXP192_init(); + strcat_P(features, " PMU"); +#endif + + // scan i2c bus for devices + i2c_scan(); + #endif // verbose // read (and initialize on first run) runtime settings from NVRAM loadConfig(); // includes initialize if necessary +// initialize display +#ifdef HAS_DISPLAY + strcat_P(features, " OLED"); + DisplayIsOn = cfg.screenon; + init_display(PRODUCTNAME, PROGVERSION); // note: blocking call +#endif + #ifdef BOARD_HAS_PSRAM assert(psramFound()); ESP_LOGI(TAG, "PSRAM found and initialized"); strcat_P(features, " PSRAM"); #endif -// set external power mode -#ifdef EXT_POWER_SW - pinMode(EXT_POWER_SW, OUTPUT); - digitalWrite(EXT_POWER_SW, EXT_POWER_ON); - strcat_P(features, " VEXT"); -#endif - #ifdef BAT_MEASURE_EN pinMode(BAT_MEASURE_EN, OUTPUT); #endif @@ -195,17 +217,25 @@ void setup() { #if (HAS_LED != NOT_A_PIN) pinMode(HAS_LED, OUTPUT); strcat_P(features, " LED"); + +#ifdef LED_POWER_SW + pinMode(LED_POWER_SW, OUTPUT); + digitalWrite(LED_POWER_SW, LED_POWER_ON); +#endif + #ifdef HAS_TWO_LED pinMode(HAS_TWO_LED, OUTPUT); strcat_P(features, " LED1"); #endif + // use LED for power display if we have additional RGB LED, else for status #ifdef HAS_RGB_LED switch_LED(LED_ON); strcat_P(features, " RGB"); rgb_set_color(COLOR_PINK); #endif -#endif + +#endif // HAS_LED #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) // start led loop @@ -227,7 +257,7 @@ void setup() { #endif // initialize battery status -#ifdef BAT_MEASURE_ADC +#if (defined BAT_MEASURE_ADC || defined HAS_PMU) strcat_P(features, " BATT"); calibrate_voltage(); batt_voltage = read_voltage(); @@ -297,13 +327,6 @@ void setup() { strcat_P(features, " FILTER"); #endif -// initialize display -#ifdef HAS_DISPLAY - strcat_P(features, " OLED"); - DisplayIsOn = cfg.screenon; - init_display(PRODUCTNAME, PROGVERSION); // note: blocking call -#endif - // initialize matrix display #ifdef HAS_MATRIX_DISPLAY strcat_P(features, " LED_MATRIX"); @@ -419,12 +442,10 @@ void setup() { #warning you did not specify a time source, time will not be synched #endif -/* // initialize gps time #if (HAS_GPS) fetch_gpsTime(); #endif -*/ #if (defined HAS_IF482 || defined HAS_DCF77) ESP_LOGI(TAG, "Starting Clock Controller..."); @@ -445,18 +466,11 @@ void setup() { ESP_LOGI(TAG, "Features:%s", features); macsniff_setup(); - + uart_setup(); } // setup() -void loop() { +} // setup() - while (1) { -#if (HAS_LORA) - os_runloop_once(); // execute lmic scheduled jobs and events -#else - delay(2); // yield to CPU -#endif - } -} +void loop() { vTaskDelete(NULL); } diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 6d5eb508..f7826a9a 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -1,3 +1,5 @@ +// clang-format off + // ----- Paxcounter user config file ------ // // --> adapt to your needs and use case <-- @@ -46,7 +48,8 @@ #define MEM_LOW 2048 // [Bytes] low memory threshold triggering a send cycle #define RETRANSMIT_RCMD 5 // [seconds] wait time before retransmitting rcommand results #define PAYLOAD_BUFFER_SIZE 51 // maximum size of payload block per transmit -#define LORASFDEFAULT 9 // 7 ... 12 SF, according to LoRaWAN specs +#define LORADRDEFAULT 5 // 0 .. 15, LoRaWAN datarate, according to regional LoRaWAN specs [default = 5] +#define LORATXPOWDEFAULT 7 // 0 .. 255, LoRaWAN TX power in dBm [default = 14] #define MAXLORARETRY 500 // maximum count of TX retries if LoRa busy #define SEND_QUEUE_SIZE 10 // maximum number of messages in payload send queue [1 = no queue] @@ -72,7 +75,7 @@ #define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off #define TIME_SYNC_COMPILEDATE 0 // set to 1 to use compile date to initialize RTC after power outage [default = 0] #define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 0] -#define TIME_SYNC_LORASERVER 1 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0] +#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0] // settings for syncing time with timeserver applications #define TIME_SYNC_SAMPLES 1 // number of time requests for averaging @@ -85,6 +88,7 @@ // Ports on which the device sends and listenes on LoRaWAN and SPI #define COUNTERPORT 1 // counts +#define MACPORT 0 // network commands #define RCMDPORT 2 // remote commands #define STATUSPORT 2 // remote command results #define CONFIGPORT 3 // config query results @@ -107,4 +111,4 @@ #define CAYENNE_ACTUATOR 10 // actuator commands #define CAYENNE_DEVICECONFIG 11 // device period configuration #define CAYENNE_SENSORREAD 13 // sensor period configuration -#define CAYENNE_SENSORENABLE 14 // sensor enable configuration \ No newline at end of file +#define CAYENNE_SENSORENABLE 14 // sensor enable configuration diff --git a/src/payload.cpp b/src/payload.cpp index ac310c55..e755f077 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -43,7 +43,7 @@ void PayloadConvert::addVoltage(uint16_t value) { } void PayloadConvert::addConfig(configData_t value) { - buffer[cursor++] = value.lorasf; + buffer[cursor++] = value.loradr; buffer[cursor++] = value.txpower; buffer[cursor++] = value.adrmode; buffer[cursor++] = value.screensaver; @@ -164,7 +164,7 @@ void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) { void PayloadConvert::addVoltage(uint16_t value) { writeUint16(value); } void PayloadConvert::addConfig(configData_t value) { - writeUint8(value.lorasf); + writeUint8(value.loradr); writeUint8(value.txpower); writeUint16(value.rssilimit); writeUint8(value.sendcycle); @@ -378,7 +378,7 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius, uint32_t mem, uint8_t reset1, uint8_t reset2) { uint16_t temp = celsius * 10; uint16_t volt = voltage / 10; -#ifdef BAT_MEASURE_ADC +#if (defined BAT_MEASURE_ADC || defined HAS_PMU) #if (PAYLOAD_ENCODER == 3) buffer[cursor++] = LPP_BATT_CHANNEL; #endif diff --git a/src/power.cpp b/src/power.cpp new file mode 100644 index 00000000..83748286 --- /dev/null +++ b/src/power.cpp @@ -0,0 +1,231 @@ +// Basic config +#include "globals.h" +#include "power.h" + +// Local logging tag +static const char TAG[] = __FILE__; + +#ifdef HAS_PMU + +AXP20X_Class pmu; + +void power_event_IRQ(void) { + + if (!I2C_MUTEX_LOCK()) + ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0); + else { + + pmu.readIRQ(); + // put your power event handler code here + + if (pmu.isVbusOverVoltageIRQ()) + ESP_LOGI(TAG, "USB voltage %.2fV too high.", pmu.getVbusVoltage() / 1000); + if (pmu.isVbusPlugInIRQ()) + ESP_LOGI(TAG, "USB plugged, %.2fV @ %.0mA", pmu.getVbusVoltage() / 1000, + pmu.getVbusCurrent()); + if (pmu.isVbusRemoveIRQ()) + ESP_LOGI(TAG, "USB unplugged."); + + if (pmu.isBattPlugInIRQ()) + ESP_LOGI(TAG, "Battery is connected."); + if (pmu.isBattRemoveIRQ()) + ESP_LOGI(TAG, "Battery was removed."); + if (pmu.isChargingIRQ()) + ESP_LOGI(TAG, "Battery charging."); + if (pmu.isChargingDoneIRQ()) + ESP_LOGI(TAG, "Battery charging done."); + if (pmu.isBattTempLowIRQ()) + ESP_LOGI(TAG, "Battery high temperature."); + if (pmu.isBattTempHighIRQ()) + ESP_LOGI(TAG, "Battery low temperature."); + + // display on/off + if (pmu.isPEKShortPressIRQ()) { + cfg.screenon = !cfg.screenon; + } + + // shutdown power + if (pmu.isPEKLongtPressIRQ()) { + AXP192_power(false); // switch off Lora, GPS, display + pmu.shutdown(); + } + + pmu.clearIRQ(); + I2C_MUTEX_UNLOCK(); + } // mutex + + // refresh stored voltage value + read_voltage(); +} + +void AXP192_power(bool on) { + if (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.setChgLEDMode(AXP20X_LED_LOW_LEVEL); + pmu.setChgLEDMode(AXP20X_LED_BLINK_1HZ); + } else { + pmu.setChgLEDMode(AXP20X_LED_OFF); + pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); + pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); + pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); + } +} + +void AXP192_showstatus(void) { + + if (!I2C_MUTEX_LOCK()) + ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0); + else { + + if (pmu.isBatteryConnect()) + if (pmu.isChargeing()) + ESP_LOGI(TAG, "Battery charging, %.2fV @ %.0fmAh", + pmu.getBattVoltage() / 1000, pmu.getBattChargeCurrent()); + else + ESP_LOGI(TAG, "Battery not charging"); + else + ESP_LOGI(TAG, "No Battery"); + + if (pmu.isVBUSPlug()) + ESP_LOGI(TAG, "USB powered, %.0fmW", + pmu.getVbusVoltage() / 1000 * pmu.getVbusCurrent()); + else + ESP_LOGI(TAG, "USB not present"); + + I2C_MUTEX_UNLOCK(); + } // mutex +} + +void AXP192_init(void) { + + // block i2c bus access + if (I2C_MUTEX_LOCK()) { + + if (pmu.begin(Wire, AXP192_PRIMARY_ADDRESS)) + ESP_LOGI(TAG, "AXP192 PMU initialization failed"); + else { + + // configure AXP192 + pmu.setDCDC1Voltage(3300); // for external OLED display + pmu.setTimeOutShutdown(false); // no automatic shutdown + pmu.setTSmode(AXP_TS_PIN_MODE_DISABLE); // TS pin mode off to save power + + // switch ADCs on + pmu.adc1Enable(AXP202_BATT_VOL_ADC1, true); + pmu.adc1Enable(AXP202_BATT_CUR_ADC1, true); + pmu.adc1Enable(AXP202_VBUS_VOL_ADC1, true); + pmu.adc1Enable(AXP202_VBUS_CUR_ADC1, true); + + // switch power rails on + AXP192_power(true); + + // I2C access of AXP202X library currently is not mutexable + // so we better should disable AXP interrupts... ? +#ifdef PMU_INT + pinMode(PMU_INT, INPUT_PULLUP); + 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, + 1); + pmu.clearIRQ(); +#endif // PMU_INT + + ESP_LOGI(TAG, "AXP192 PMU initialized"); + } + I2C_MUTEX_UNLOCK(); // release i2c bus access + } else + ESP_LOGE(TAG, "I2c bus busy - PMU initialization error"); +} +#endif // HAS_PMU + +#ifdef BAT_MEASURE_ADC +esp_adc_cal_characteristics_t *adc_characs = + (esp_adc_cal_characteristics_t *)calloc( + 1, sizeof(esp_adc_cal_characteristics_t)); + +#ifndef BAT_MEASURE_ADC_UNIT // ADC1 +static const adc1_channel_t adc_channel = BAT_MEASURE_ADC; +#else // ADC2 +static const adc2_channel_t adc_channel = BAT_MEASURE_ADC; +#endif +static const adc_atten_t atten = ADC_ATTEN_DB_11; +static const adc_unit_t unit = ADC_UNIT_1; + +#endif // BAT_MEASURE_ADC + +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)); +#else // ADC2 + // ESP_ERROR_CHECK(adc2_config_width(ADC_WIDTH_BIT_12)); + ESP_ERROR_CHECK(adc2_config_channel_atten(adc_channel, atten)); +#endif + // calibrate ADC + esp_adc_cal_value_t val_type = esp_adc_cal_characterize( + unit, atten, ADC_WIDTH_BIT_12, DEFAULT_VREF, adc_characs); + // show ADC characterization base + if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { + ESP_LOGI(TAG, + "ADC characterization based on Two Point values stored in eFuse"); + } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + ESP_LOGI(TAG, + "ADC characterization based on reference voltage stored in eFuse"); + } else { + ESP_LOGI(TAG, "ADC characterization based on default reference voltage"); + } +#endif +} + +bool batt_sufficient() { +#if (defined HAS_PMU || defined BAT_MEASURE_ADC) + uint16_t volts = read_voltage(); + return ((volts < 1000) || + (volts > OTA_MIN_BATT)); // no battery or battery sufficient +#else + return true; +#endif +} + +uint16_t read_voltage() { + uint16_t voltage = 0; + +#ifdef HAS_PMU + if (!I2C_MUTEX_LOCK()) + ESP_LOGW(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0); + else { + voltage = pmu.isVBUSPlug() ? 0xffff : pmu.getBattVoltage(); + I2C_MUTEX_UNLOCK(); + } +#else + +#ifdef BAT_MEASURE_ADC + // multisample ADC + uint32_t adc_reading = 0; + int adc_buf = 0; + for (int i = 0; i < NO_OF_SAMPLES; i++) { +#ifndef BAT_MEASURE_ADC_UNIT // ADC1 + adc_reading += adc1_get_raw(adc_channel); +#else // ADC2 + ESP_ERROR_CHECK(adc2_get_raw(adc_channel, ADC_WIDTH_BIT_12, &adc_buf)); + adc_reading += adc_buf; +#endif + } + adc_reading /= NO_OF_SAMPLES; + // Convert ADC reading to voltage in mV + voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_characs); +#endif // BAT_MEASURE_ADC + +#ifdef BAT_VOLTAGE_DIVIDER + voltage *= BAT_VOLTAGE_DIVIDER; +#endif // BAT_VOLTAGE_DIVIDER + +#endif // HAS_PMU + + return voltage; +} \ No newline at end of file diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 415283a9..e8b9f3da 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -19,32 +19,25 @@ void do_reset() { void set_reset(uint8_t val[]) { switch (val[0]) { case 0: // restart device - sprintf(display_line6, "Reset pending"); do_reset(); break; case 1: // reset MAC counter ESP_LOGI(TAG, "Remote command: reset MAC counter"); reset_counters(); // clear macs get_salt(); // get new salt - sprintf(display_line6, "Reset counter"); break; case 2: // reset device to factory settings ESP_LOGI(TAG, "Remote command: reset device to factory settings"); - sprintf(display_line6, "Factory reset"); eraseConfig(); break; case 3: // reset send queues ESP_LOGI(TAG, "Remote command: flush send queue"); - sprintf(display_line6, "Queue reset"); flushQueues(); break; case 9: // reset and ask for software update via Wifi OTA ESP_LOGI(TAG, "Remote command: software update via Wifi"); #if (USE_OTA) - sprintf(display_line6, "Software update"); cfg.runmode = 1; -#else - sprintf(display_line6, "Software update not implemented"); #endif // USE_OTA break; @@ -127,10 +120,34 @@ void set_gps(uint8_t val[]) { if (val[0]) { cfg.payloadmask |= (uint8_t)GPS_DATA; // set bit in mask } else { - cfg.payloadmask &= ~(uint8_t)GPS_DATA; // clear bit in mask + cfg.payloadmask &= (uint8_t)~GPS_DATA; // clear bit in mask } } +void set_bme(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: set BME mode to %s", val[0] ? "on" : "off"); + if (val[0]) { + cfg.payloadmask |= (uint8_t)MEMS_DATA; // set bit in mask + } else { + cfg.payloadmask &= (uint8_t)~MEMS_DATA; // clear bit in mask + } +} + +void set_batt(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: set battery mode to %s", + val[0] ? "on" : "off"); + if (val[0]) { + cfg.payloadmask |= (uint8_t)BATT_DATA; // set bit in mask + } else { + cfg.payloadmask &= (uint8_t)~BATT_DATA; // clear bit in mask + } +} + +void set_payloadmask(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: set payload mask to %X", val[0]); + cfg.payloadmask = val[0]; +} + void set_sensor(uint8_t val[]) { #if (HAS_SENSORS) switch (val[0]) { // check if valid sensor number 1...4 @@ -169,10 +186,22 @@ void set_monitor(uint8_t val[]) { cfg.monitormode = val[0] ? 1 : 0; } -void set_lorasf(uint8_t val[]) { +void set_loradr(uint8_t val[]) { #if (HAS_LORA) - ESP_LOGI(TAG, "Remote command: set LoRa SF to %d", val[0]); - switch_lora(val[0], cfg.txpower); + if (validDR(val[0])) { + cfg.loradr = val[0]; + ESP_LOGI(TAG, "Remote command: set LoRa Datarate to %d", cfg.loradr); + LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower); + ESP_LOGI(TAG, "Radio parameters now %s / %s / %s", + getSfName(updr2rps(LMIC.datarate)), + getBwName(updr2rps(LMIC.datarate)), + getCrName(updr2rps(LMIC.datarate))); + + } else + ESP_LOGI( + TAG, + "Remote command: set LoRa Datarate called with illegal datarate %d", + val[0]); #else ESP_LOGW(TAG, "Remote command: LoRa not implemented"); #endif // HAS_LORA @@ -223,8 +252,16 @@ void set_rgblum(uint8_t val[]) { void set_lorapower(uint8_t val[]) { #if (HAS_LORA) - ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %d", val[0]); - switch_lora(cfg.lorasf, val[0]); + // set data rate and transmit power only if we have no ADR + if (!cfg.adrmode) { + cfg.txpower = val[0]; + ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %d", cfg.txpower); + LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower); + } else + ESP_LOGI( + TAG, + "Remote command: set LoRa TXPOWER, not executed because ADR is on"); + #else ESP_LOGW(TAG, "Remote command: LoRa not implemented"); #endif // HAS_LORA @@ -239,14 +276,10 @@ void get_config(uint8_t val[]) { void get_status(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get device status"); -#ifdef BAT_MEASURE_ADC - uint16_t voltage = read_voltage(); -#else - uint16_t voltage = 0; -#endif payload.reset(); - payload.addStatus(voltage, uptime() / 1000, temperatureRead(), getFreeRAM(), - rtc_get_reset_reason(0), rtc_get_reset_reason(1)); + payload.addStatus(read_voltage(), uptime() / 1000, temperatureRead(), + getFreeRAM(), rtc_get_reset_reason(0), + rtc_get_reset_reason(1)); SendPayload(STATUSPORT, prio_high); }; @@ -274,6 +307,17 @@ void get_bme(uint8_t val[]) { #endif }; +void get_batt(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: get battery voltage"); +#if (defined BAT_MEASURE_ADC || defined HAS_PMU) + payload.reset(); + payload.addVoltage(read_voltage()); + SendPayload(BATTPORT, prio_normal); +#else + ESP_LOGW(TAG, "Battery voltage not supported"); +#endif +}; + void get_time(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get time"); payload.reset(); @@ -297,26 +341,28 @@ void set_flush(uint8_t val[]) { // format: opcode, function, #bytes params, // flag (true = do make settings persistent / false = don't) // -cmd_t table[] = { +static 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_lorasf, 1, true}, {0x06, set_lorapower, 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, true}, {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}, {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}, {0x80, get_config, 0, false}, - {0x81, get_status, 0, false}, {0x84, get_gps, 0, false}, + {0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true}, + {0x15, set_bme, 1, true}, {0x16, set_batt, 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}}; -const uint8_t cmdtablesize = +static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table // check and execute remote command -void rcommand(uint8_t cmd[], uint8_t cmdlength) { +void rcommand(const uint8_t cmd[], const uint8_t cmdlength) { if (cmdlength == 0) return; diff --git a/src/senddata.cpp b/src/senddata.cpp index fd5df963..76d412e2 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -10,9 +10,12 @@ void sendcycle() { // put data to send in RTos Queues used for transmit over channels Lora and SPI void SendPayload(uint8_t port, sendprio_t prio) { - MessageBuffer_t SendBuffer; // contains MessageSize, MessagePort, Message[] + MessageBuffer_t + SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[] SendBuffer.MessageSize = payload.getSize(); + SendBuffer.MessagePrio = prio; + switch (PAYLOAD_ENCODER) { case 1: // plain -> no mapping case 2: // packed -> no mapping @@ -38,20 +41,20 @@ void SendPayload(uint8_t port, sendprio_t prio) { default: SendBuffer.MessagePort = port; } - memcpy(SendBuffer.Message, payload.getBuffer(), payload.getSize()); + memcpy(SendBuffer.Message, payload.getBuffer(), SendBuffer.MessageSize); // enqueue message in device's send queues #if (HAS_LORA) - lora_enqueuedata(&SendBuffer, prio); + lora_enqueuedata(&SendBuffer); #endif #ifdef HAS_SPI - spi_enqueuedata(&SendBuffer, prio); + spi_enqueuedata(&SendBuffer); #endif } // SendPayload // interrupt triggered function to prepare payload to send -void sendCounter() { +void sendData() { uint8_t bitmask = cfg.payloadmask; uint8_t mask = 1; @@ -115,7 +118,7 @@ void sendCounter() { break; #endif -#ifdef BAT_MEASURE_ADC +#if (defined BAT_MEASURE_ADC || defined HAS_PMU) case BATT_DATA: payload.reset(); payload.addVoltage(read_voltage()); @@ -128,7 +131,7 @@ void sendCounter() { mask <<= 1; } // while (bitmask) -} // sendCounter() +} // sendData() void flushQueues() { #if (HAS_LORA) diff --git a/src/spislave.cpp b/src/spislave.cpp index 9801058b..4d3a75ab 100644 --- a/src/spislave.cpp +++ b/src/spislave.cpp @@ -56,7 +56,7 @@ void spi_slave_task(void *param) { memset(txbuf, 0, sizeof(txbuf)); memset(rxbuf, 0, sizeof(rxbuf)); - // wait until data to send arrivey + // fetch next or wait for payload to send from queue if (xQueueReceive(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) { ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); continue; @@ -147,10 +147,12 @@ esp_err_t spi_init() { return ret; } -void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) { +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 diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 4d01f7d9..85f62864 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -54,12 +54,64 @@ void calibrateTime(void) { } #endif + goto finish; + finish: - setMyTime(t, t_msec, timeSource); // set time + setMyTime((uint32_t)t, t_msec, timeSource); // set time } // calibrateTime() +// adjust system time, calibrate RTC and RTC_INT pps +void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, + timesource_t mytimesource) { + + // called with invalid timesource? + if (mytimesource == _unsynced) + return; + + // increment t_sec only if t_msec > 1000 + time_t time_to_set = (time_t)(t_sec + t_msec / 1000); + + // do we have a valid time? + if (timeIsValid(time_to_set)) { + + // if we have msec fraction, then wait until top of second with + // millisecond precision + if (t_msec % 1000) { + time_to_set++; + vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000)); + } + + ESP_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0, + time_to_set, t_msec % 1000); + +// if we have got an external timesource, set RTC time and shift RTC_INT pulse +// to top of second +#ifdef HAS_RTC + if ((mytimesource == _gps) || (mytimesource == _lora)) + set_rtctime(time_to_set); +#endif + +// if we have a software pps timer, shift it to top of second +#if (!defined GPS_INT && !defined RTC_INT) + timerWrite(ppsIRQ, 0); // reset pps timer + CLOCKIRQ(); // fire clock pps, this advances time 1 sec +#endif + + setTime(time_to_set); // set the time on top of second + + timeSource = mytimesource; // set global variable + timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); + ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c", + millis() / 1000.0, timeSetSymbols[timeSource]); + } else { + timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); + ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c", + millis() / 1000.0, timeSetSymbols[timeSource]); + } +} + // helper function to setup a pulse per second for time synchronisation uint8_t timepulse_init() { diff --git a/src/timesync.cpp b/src/timesync.cpp index e51454eb..ae73bd0c 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -108,7 +108,7 @@ void process_timesync_req(void *taskparameter) { payload.addByte(0x99); SendPayload(RCMDPORT, prio_high); // ...send a alive open a receive window for last time_sync_answer - // LMIC_sendAlive(); + LMIC_sendAlive(); } } // end of for loop to collect timestamp samples @@ -181,7 +181,7 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { // the 5th byte contains the fractional seconds in 2^-8 second steps // (= 1/250th sec), we convert this to ms uint16_t timestamp_msec = 4 * buf[4]; - // pointers to 4 bytes 4 bytes containing UTC seconds since unix epoch, msb + // pointers to 4 bytes containing UTC seconds since unix epoch, msb uint32_t timestamp_sec, *timestamp_ptr; // convert buffer to uint32_t, octet order is big endian @@ -210,92 +210,6 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { } } -// adjust system time, calibrate RTC and RTC_INT pps -void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, - timesource_t mytimesource) { - - t_sec ++; - time_t time_to_set = (time_t)(t_sec); - - // increment t_sec only if t_msec > 1000 - time_to_set = (time_t)(t_sec + t_msec / 1000); - - // do we have a valid time? - if (timeIsValid(time_to_set)) { - - // if we have msec fraction, then wait until top of second with - // millisecond precision - if (t_msec % 1000) { - time_to_set++; - vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000)); - } - - ESP_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0, - time_to_set, t_msec % 1000); - -// if we have got an external timesource, set RTC time and shift RTC_INT pulse -// to top of second -#ifdef HAS_RTC - if ((mytimesource == _gps) || (mytimesource == _lora)) - set_rtctime(time_to_set); -#endif - -// if we have a software pps timer, shift it to top of second -#if (!defined GPS_INT && !defined RTC_INT) - timerWrite(ppsIRQ, 0); // reset pps timer - CLOCKIRQ(); // fire clock pps, this advances time 1 sec -#endif - - struct timeval tv; - struct timezone tz; - if(gettimeofday(&tv, &tz) != 0) { - ESP_LOGE(TAG, "ERROR gettimeofday"); - } - struct timeval beforeTime = tv; - - struct timeval nowTime; - nowTime.tv_sec = t_sec; - nowTime.tv_usec = t_msec; - if(settimeofday(&nowTime, &tz) != 0) { - ESP_LOGE(TAG, "ERROR settimeofday"); - } - - struct timeval diff; - diff.tv_sec = nowTime.tv_sec-beforeTime.tv_sec; - diff.tv_usec = nowTime.tv_usec-beforeTime.tv_usec; - - // sum up diff_s and diff_ms to one ms value - int32_t diff_s = diff.tv_sec; - int32_t diff_ms = diff.tv_usec/1000; - int32_t diff_ms_remain = diff_ms / 1000; - diff_s += diff_ms_remain; - diff_ms += -1000*diff_ms_remain; - if(diff_ms < 0) { - diff_s --; - diff_ms += 1000; - } - // cap diff at 24h (= 86,400s) - diff_s = diff_s % 86400; - int32_t timediff_ms = diff_s * 1000 + diff_ms; - - // send diffTime - payload.reset(); - payload.addTimeDiff(timediff_ms); - SendPayload(TIMEDIFFPORT, prio_high); - ESP_LOGI(TAG, "timediff_ms: %d", timediff_ms); - - timeSource = mytimesource; // set global variable - timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); - time_uart_send_start(); - ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c", - millis() / 1000.0, timeSetSymbols[timeSource]); - } else { - timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); - ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c", - millis() / 1000.0, timeSetSymbols[timeSource]); - } -} - // create task for timeserver handshake processing, called from main.cpp void timesync_init() { xTaskCreatePinnedToCore(process_timesync_req, // task function diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index 85fdf6a1..b22fe5f2 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -57,7 +57,7 @@ void wifi_sniffer_init(void) { 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 = { + // 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 @@ -71,9 +71,9 @@ void wifi_sniffer_init(void) { ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM)); // we don't need NVRAM ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); - ESP_ERROR_CHECK(esp_wifi_stop()); - ESP_ERROR_CHECK( - esp_wifi_set_promiscuous_filter(&filter)); // set frame filter + ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE)); // no modem power saving + ESP_ERROR_CHECK(esp_wifi_start()); // must be started to be able to switch ch + ESP_ERROR_CHECK(esp_wifi_set_promiscuous_filter(&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