diff --git a/README.md b/README.md index 93b11a8d..3940f6c8 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,16 @@ By default bluetooth sniffing not installed (#define *BLECOUNTER* 0 in paxcounte # Preparing +## Install Platformio + +Install PlatformIO IDE for embedded development to make this project. Platformio integrates with your favorite IDE, choose eg. Visual Studio, Atom, Eclipse etc. + Compile time configuration is spread across several files. Before compiling the code, edit or create the following files: -## platformio_orig.ini -Edit `platformio_orig.ini` and select desired hardware target in section boards. To add a new board, create an appropriate hardware abstraction layer file in hal subdirectory, and add a pointer to this file in sections board. Copy or rename to `platformio.ini`. +## platformio.ini +Edit `platformio_orig.ini` and select desired hardware target in section boards. To add a new board, create an appropriate hardware abstraction layer file in hal subdirectory, and add a pointer to this file in sections board. Copy or rename to `platformio.ini` in the root directory of the project. Now start Platformio. Note: Platformio is looking for `platformio.ini` in the root directory and won't start if it does not find this file. -## src/paxcounter_orig.conf +## src/paxcounter.conf Edit `src/paxcounter_orig.conf` and tailor settings in this file according to your needs and use case. Please take care of the duty cycle regulations of the LoRaWAN network you're going to use. Copy or rename to `src/paxcounter.conf`. If your device has a **real time clock** it can be updated bei either LoRaWAN network or GPS time, according to settings *TIME_SYNC_INTERVAL* and *TIME_SYNC_LORAWAN* in `paxcounter.conf`. @@ -205,9 +209,9 @@ Follow all steps so far for preparing the device, use the packed payload format. There in the sensor configuration select "TheThingsNetwork" and set Decoding Profil to "LoRa serialization", enter your TTN Application and Device Id. Decoding option has to be [{"decoder":"latLng"},{"decoder":"uint16","sensor_id":"yoursensorid"}] -# Covid-19 Exposure Notification System beacon detection (Germany: "Corona Warn App counter") +# Covid-19 Exposure Notification System beacon detection -Bluetooth low energy service UUID 0xFD6F, used by Google/Apple COVID-19 Exposure Notification System, can be monitored and counted. By comparing with the total number of observed devices this gives an indication how many people staying in proximity are using Apps for tracing COVID-19 exposures, e.g. in Germany the "Corona Warn App". To achive best resulta withs this funcion, use following settings in [paxcounter.conf](src/paxcounter.conf): +Bluetooth low energy service UUID 0xFD6F, used by Google/Apple COVID-19 Exposure Notification System, can be monitored and counted. By comparing with the total number of observed devices this gives an indication how many people staying in proximity are using Apps for tracing COVID-19 exposures, e.g. in Germany the "Corona Warn App". To achive best results with this funcion, use following settings in [paxcounter.conf](src/paxcounter.conf): #define COUNT_ENS 1 // enable ENS monitoring function #define VENDORFILTER 0 // disable OUI filter (scans ALL device MACs) @@ -504,6 +508,11 @@ Send for example `8386` as Downlink on Port 2 to get battery status and time/dat 0 = disabled 1 = enabled [default] + +0x18 set ENS counter on/off + + 0 = disabled [default] + 1 = enabled 0x80 get device configuration diff --git a/include/bmesensor.h b/include/bmesensor.h index d8ffaa7b..c0d17475 100644 --- a/include/bmesensor.h +++ b/include/bmesensor.h @@ -61,7 +61,7 @@ const uint8_t bsec_config_iaq[454] = { // Helper functions declarations int bme_init(); -void bmecycle(void); +void setBMEIRQ(void); void bme_storedata(bmeStatus_t *bme_store); int checkIaqSensorStatus(void); void loadState(void); diff --git a/include/configmanager.h b/include/configmanager.h index 373cc1af..d196e6ab 100644 --- a/include/configmanager.h +++ b/include/configmanager.h @@ -1,11 +1,12 @@ #ifndef _CONFIGMANAGER_H #define _CONFIGMANAGER_H -#include -#include +#include "globals.h" +#include +void saveConfig(bool erase = false); +bool loadConfig(void); void eraseConfig(void); -void saveConfig(void); -void loadConfig(void); +int version_compare(const String v1, const String v2); #endif \ No newline at end of file diff --git a/include/cyclic.h b/include/cyclic.h index 0fdf38b1..6bd9b2a7 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -11,9 +11,9 @@ #include "sds011read.h" #include "sdcard.h" -extern Ticker housekeeper; +extern Ticker cyclicTimer; -void housekeeping(void); +void setCyclicIRQ(void); void doHousekeeping(void); uint64_t uptime(void); void reset_counters(void); diff --git a/include/globals.h b/include/globals.h index 14ca8221..324c5328 100644 --- a/include/globals.h +++ b/include/globals.h @@ -60,7 +60,10 @@ enum runmode_t { }; // Struct holding devices's runtime configuration -typedef struct { +// using packed to avoid compiler padding, because struct will be memcpy'd to +// byte array +typedef struct __attribute__((packed)) { + char version[10]; // Firmware version uint8_t loradr; // 0-15, lora datarate uint8_t txpower; // 2-15, lora tx power uint8_t adrmode; // 0=disabled, 1=enabled @@ -79,9 +82,12 @@ typedef struct { uint8_t monitormode; // 0=disabled, 1=enabled uint8_t runmode; // 0=normal, 1=update uint8_t payloadmask; // bitswitches for payload data - char version[10]; // Firmware version + uint8_t enscount; // 0=disabled 1= enabled + +#ifdef HAS_BME680 uint8_t bsecstate[BSEC_MAX_STATE_BLOB_SIZE + 1]; // BSEC state for BME680 sensor +#endif } configData_t; // Struct holding payload for data send queue diff --git a/include/mqttclient.h b/include/mqttclient.h index 5f942aa3..9b7ac649 100644 --- a/include/mqttclient.h +++ b/include/mqttclient.h @@ -26,7 +26,7 @@ extern TaskHandle_t mqttTask; void mqtt_enqueuedata(MessageBuffer_t *message); void mqtt_queuereset(void); -void mqtt_irq(void); +void setMqttIRQ(void); void mqtt_loop(void); void mqtt_client_task(void *param); int mqtt_connect(const char *my_host, const uint16_t my_port); diff --git a/include/ota.h b/include/ota.h index 900ef530..9e53cae1 100644 --- a/include/ota.h +++ b/include/ota.h @@ -6,17 +6,15 @@ #include "globals.h" #include "led.h" #include "display.h" +#include "configmanager.h" #include #include #include #include -#include -#include int do_ota_update(); void start_ota_update(); -int version_compare(const String v1, const String v2); void ota_display(const uint8_t row, const std::string status, const std::string msg); void show_progress(unsigned long current, unsigned long size); diff --git a/include/senddata.h b/include/senddata.h index f1a43867..42686688 100644 --- a/include/senddata.h +++ b/include/senddata.h @@ -10,12 +10,12 @@ #include "sdcard.h" #include "corona.h" -extern Ticker sendcycler; +extern Ticker sendTimer; void SendPayload(uint8_t port, sendprio_t prio); void sendData(void); void checkSendQueues(void); void flushQueues(); -void sendcycle(void); +void setSendIRQ(void); #endif // _SENDDATA_H_ diff --git a/include/timekeeper.h b/include/timekeeper.h index 035cec6a..f9455746 100644 --- a/include/timekeeper.h +++ b/include/timekeeper.h @@ -17,7 +17,7 @@ void IRAM_ATTR CLOCKIRQ(void); void clock_init(void); void clock_loop(void *pvParameters); void timepulse_start(void); -void timeSync(void); +void setTimeSyncIRQ(void); uint8_t timepulse_init(void); time_t timeIsValid(time_t const t); void calibrateTime(void); diff --git a/platformio_orig.ini b/platformio_orig.ini index 0edbc702..c3e9d878 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -46,7 +46,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 2.0.15 +release_version = 2.0.2 ; 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 @@ -61,7 +61,7 @@ display_library = ; set by build.py and taken from hal file lib_deps_lora = mcci-catena/MCCI LoRaWAN LMIC library @ ^3.2.0 lib_deps_display = - bitbank2/OneBitDisplay @ 1.5.0 + bitbank2/OneBitDisplay @ 1.7.2 ricmoo/QRCode @ ^0.0.1 bodmer/TFT_eSPI @ ^2.2.20 lib_deps_ledmatrix = @@ -72,7 +72,7 @@ lib_deps_gps = mikalhart/TinyGPSPlus @ ^1.0.2 lib_deps_sensors = adafruit/Adafruit Unified Sensor @ ^1.1.4 - adafruit/Adafruit BME280 Library @ ^2.1.0 + adafruit/Adafruit BME280 Library @ ^2.1.1 adafruit/Adafruit BMP085 Library @ ^1.1.0 boschsensortec/BSEC Software Library @ 1.5.1474 https://github.com/ricki-z/SDS011.git @@ -95,10 +95,11 @@ lib_deps_all = build_flags_basic = -include "src/hal/${board.halfile}" -include "src/paxcounter.conf" - -w '-DCORE_DEBUG_LEVEL=${common.debug_level}' '-DLOG_LOCAL_LEVEL=${common.debug_level}' '-DPROGVERSION="${common.release_version}"' + '-Wno-unknown-pragmas' + '-Wno-unused-variable' build_flags_sensors = -Llib/Bosch-BSEC/src/esp32/ -lalgobsec diff --git a/src/TTN/packed_decoder.js b/src/TTN/packed_decoder.js index 97dfbcde..fef75870 100644 --- a/src/TTN/packed_decoder.js +++ b/src/TTN/packed_decoder.js @@ -257,7 +257,7 @@ var bitmap2 = function (byte) { } var i = bytesToInt(byte); var bm = ('00000000' + Number(i).toString(2)).substr(-8).split('').map(Number).map(Boolean); - return ['gps', 'alarm', 'bme', 'counter', 'sensor1', 'sensor2', 'sensor3', 'battery'] + return ['battery', 'sensor3', 'sensor2', 'sensor1', 'counter', 'bme', 'alarm', 'gps'] .reduce(function (obj, pos, index) { obj[pos] = +bm[index]; return obj; diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index a9e3c58a..7cbd398c 100644 --- a/src/Timeserver/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -1,41 +1,4 @@ [ - { - "id": "b8bd33fd.61caa", - "type": "function", - "z": "449c1517.e25f4c", - "name": "Timeserver Logic", - "func": "/* LoRaWAN Timeserver\n\nVERSION: 1.3\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1 sequence number (taken from node's time_sync_req)\n2..5 current second (from GPS epoch starting 1980)\n6 1/250ths fractions of current second\n\n*/\n\nfunction timecompare(a, b) {\n \n const timeA = a.time;\n const timeB = b.time;\n\n let comparison = 0;\n if (timeA > timeB) {\n comparison = 1;\n } else if (timeA < timeB) {\n comparison = -1;\n }\n return comparison;\n}\n\nlet confidence = 1000; // max millisecond diff gateway time to server time\n\n// guess if we have received a valid time_sync_req command\nif (msg.payload.payload_raw.length != 1)\n return;\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar seqNo = msg.payload.payload_raw[0];\nvar seqNoMsg = { payload: seqNo };\nvar gateway_list = msg.payload.metadata.gateways;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.payload.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) {\n var notavailMsg = { payload: \"n/a\" };\n var notimeMsg = { payload: 0xff }; \n var buf2 = Buffer.alloc(1);\n msg.payload = new Buffer(buf2.fill(0xff));\n msg.port = 9; // Paxcounter TIMEPORT\n return [notavailMsg, notavailMsg, deviceMsg, seqNoMsg, msg];}\n\n// sort time array in ascending order to find most recent timestamp for time answer\ngw_timestamps.sort(timecompare);\n\nvar timestamp = gw_timestamps[0].time;\nvar eui = gw_timestamps[0].eui;\nvar offset = server_time - timestamp;\n\nvar seconds = Math.floor(timestamp/1000);\nvar fractions = (timestamp % 1000) / 4;\n\nlet buf = new ArrayBuffer(6);\nnew DataView(buf).setUint8(0, seqNo);\nnew DataView(buf).setUint32(1, seconds);\nnew DataView(buf).setUint8(5, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = 9; // Paxcounter TIMEPORT\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", - "outputs": 5, - "noerr": 0, - "x": 330, - "y": 327, - "wires": [ - [ - "c9a83ac9.50fd18", - "6aeb3720.a89618", - "6ac55bbe.12ac54" - ], - [ - "de908e66.b6fd3" - ], - [ - "d5a35bab.44cb18" - ], - [ - "3a661f0a.c61b1" - ], - [ - "9b4f492d.fbfd18" - ] - ], - "outputLabels": [ - "gw_eui", - "offset_ms", - "device", - "seq_no", - "time_sync_ans" - ] - }, { "id": "9b4f492d.fbfd18", "type": "change", @@ -214,12 +177,49 @@ "id": "15980d22.6f4663", "type": "comment", "z": "449c1517.e25f4c", - "name": "LoRaWAN Timeserver v1.3", + "name": "LoRaWAN Timeserver v1.4", "info": "PLEASE NOTE: There is a patent filed for the time sync algorithm used in the\ncode of this file. The shown implementation example is covered by the\nrepository's licencse, but you may not be eligible to deploy the applied\nalgorithm in applications without granted license by the patent holder.", "x": 150, "y": 47, "wires": [] }, + { + "id": "b8bd33fd.61caa", + "type": "function", + "z": "449c1517.e25f4c", + "name": "Timeserver Logic", + "func": "/* LoRaWAN Timeserver\n\nVERSION: 1.4\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1 sequence number (taken from node's time_sync_req)\n2..5 current second (from GPS epoch starting 1980)\n6 1/250ths fractions of current second\n\n*/\n\nfunction timecompare(a, b) {\n \n const timeA = a.time;\n const timeB = b.time;\n\n let comparison = 0;\n if (timeA > timeB) {\n comparison = 1;\n } else if (timeA < timeB) {\n comparison = -1;\n }\n return comparison;\n}\n\nlet confidence = 1000; // max millisecond diff gateway time to server time\nlet TIME_SYNC_END_FLAG = 255;\n\n// guess if we have received a valid time_sync_req command\nif (msg.payload.payload_raw.length != 1)\n return;\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar seqNo = msg.payload.payload_raw[0];\nvar seqNoMsg = { payload: seqNo };\nvar gateway_list = msg.payload.metadata.gateways;\n\n// don't answer on TIME_SYNC_END_FLAG\nif (seqNo == TIME_SYNC_END_FLAG)\n return;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.payload.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) {\n var notavailMsg = { payload: \"n/a\" };\n var notimeMsg = { payload: 0xff }; \n var buf2 = Buffer.alloc(1);\n msg.payload = new Buffer(buf2.fill(0xff));\n msg.port = 9; // Paxcounter TIMEPORT\n return [notavailMsg, notavailMsg, deviceMsg, seqNoMsg, msg];}\n\n// sort time array in ascending order to find most recent timestamp for time answer\ngw_timestamps.sort(timecompare);\n\nvar timestamp = gw_timestamps[0].time;\nvar eui = gw_timestamps[0].eui;\nvar offset = server_time - timestamp;\n\nvar seconds = Math.floor(timestamp/1000);\nvar fractions = (timestamp % 1000) / 4;\n\nlet buf = new ArrayBuffer(6);\nnew DataView(buf).setUint8(0, seqNo);\nnew DataView(buf).setUint32(1, seconds);\nnew DataView(buf).setUint8(5, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = 9; // Paxcounter TIMEPORT\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", + "outputs": 5, + "noerr": 0, + "x": 330, + "y": 327, + "wires": [ + [ + "c9a83ac9.50fd18", + "6aeb3720.a89618", + "6ac55bbe.12ac54" + ], + [ + "de908e66.b6fd3" + ], + [ + "d5a35bab.44cb18" + ], + [ + "3a661f0a.c61b1" + ], + [ + "9b4f492d.fbfd18" + ] + ], + "outputLabels": [ + "gw_eui", + "offset_ms", + "device", + "seq_no", + "time_sync_ans" + ] + }, { "id": "c9a83ac9.50fd18", "type": "debug", diff --git a/src/Timeserver/timeserver.java b/src/Timeserver/timeserver.java index 5b0db293..ca81cf7a 100644 --- a/src/Timeserver/timeserver.java +++ b/src/Timeserver/timeserver.java @@ -1,6 +1,6 @@ /* LoRaWAN Timeserver -VERSION: 1.3 +VERSION: 1.4 construct 6 byte timesync_answer from gateway timestamp and node's time_sync_req @@ -26,6 +26,7 @@ function timecompare(a, b) { } let confidence = 1000; // max millisecond diff gateway time to server time +let TIME_SYNC_END_FLAG = 255; // guess if we have received a valid time_sync_req command if (msg.payload.payload_raw.length != 1) @@ -36,6 +37,10 @@ var seqNo = msg.payload.payload_raw[0]; var seqNoMsg = { payload: seqNo }; var gateway_list = msg.payload.metadata.gateways; +// don't answer on TIME_SYNC_END_FLAG +if (seqNo == TIME_SYNC_END_FLAG) + return; + // filter all gateway timestamps that have milliseconds part (which we assume have a ".") var gateways = gateway_list.filter(function (element) { return (element.time.includes(".")); diff --git a/src/blecsan.cpp b/src/blecsan.cpp index 288757a0..9a60e456 100644 --- a/src/blecsan.cpp +++ b/src/blecsan.cpp @@ -169,9 +169,11 @@ IRAM_ATTR void gap_callback_handler(esp_gap_ble_cb_event_t event, mac_add((uint8_t *)p->scan_rst.bda, p->scan_rst.rssi, MAC_SNIFF_BLE); #if (COUNT_ENS) - // check for ens signature - if (NULL != strstr((const char *)p->scan_rst.ble_adv, ensMagicBytes)) - cwa_mac_add(hashedmac); + if (cfg.enscount) { + // check for ens signature + if (NULL != strstr((const char *)p->scan_rst.ble_adv, ensMagicBytes)) + cwa_mac_add(hashedmac); + } #endif /* to be improved in vendorfilter if: diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 8e3f57ba..2cedc35d 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -44,7 +44,7 @@ Adafruit_BMP085 bmp; // I2C #endif -void bmecycle() { xTaskNotify(irqHandlerTask, BME_IRQ, eSetBits); } +void setBMEIRQ() { xTaskNotify(irqHandlerTask, BME_IRQ, eSetBits); } // initialize BME680 sensor int bme_init(void) { @@ -133,7 +133,7 @@ int bme_init(void) { finish: I2C_MUTEX_UNLOCK(); // release i2c bus access if (rc) - bmecycler.attach(BMECYCLE, bmecycle); + bmecycler.attach(BMECYCLE, setBMEIRQ); return rc; } // bme_init() diff --git a/src/configmanager.cpp b/src/configmanager.cpp index a3c9babb..f947f383 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -4,362 +4,157 @@ #include "configmanager.h" // Local logging tag -static const char TAG[] = "flash"; +static const char TAG[] = __FILE__; -nvs_handle my_handle; -esp_err_t err; +// default settings for device data to be sent +#define PAYLOADMASK \ + ((GPS_DATA | ALARM_DATA | MEMS_DATA | COUNT_DATA | SENSOR1_DATA | \ + SENSOR2_DATA | SENSOR3_DATA) & \ + (~BATT_DATA)) -#define PAYLOADMASK \ - ((GPS_DATA | ALARM_DATA | MEMS_DATA | COUNT_DATA | \ - SENSOR1_DATA | SENSOR2_DATA | SENSOR3_DATA) & \ - (~BATT_DATA) ) +// namespace for device runtime preferences +#define DEVCONFIG "paxcntcfg" -// populate cfg vars with factory settings -void defaultConfig() { - cfg.loradr = LORADRDEFAULT; // 0-15, lora datarate, see paxcounter.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 = COUNTERMODE; // 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 = +Preferences nvram; + +static const uint8_t cfgMagicBytes[] = {0x21, 0x76, 0x87, 0x32, 0xf4}; +static const size_t cfgLen = sizeof(cfg), cfgLen2 = sizeof(cfgMagicBytes); +static uint8_t buffer[cfgLen + cfgLen2]; + +// populate runtime config with device factory settings +// +// configuration frame structure in NVRAM; +// 1. version header [10 bytes, containing version string] +// 2. user settings [cfgLen bytes, containing default runtime settings +// (configData_t cfg)] +// 3. magicByte [cfgLen2 bytes, containing a fixed identifier] + +static void defaultConfig(configData_t *myconfig) { + memcpy(myconfig->version, &PROGVERSION, 10); // Firmware version + + // device factory settings + myconfig->loradr = LORADRDEFAULT; // 0-15, lora datarate, see paxcounter.conf + myconfig->txpower = LORATXPOWDEFAULT; // 0-15, lora tx power + myconfig->adrmode = 1; // 0=disabled, 1=enabled + myconfig->screensaver = 0; // 0=disabled, 1=enabled + myconfig->screenon = 1; // 0=disabled, 1=enabled + myconfig->countermode = + COUNTERMODE; // 0=cyclic, 1=cumulative, 2=cyclic confirmed + myconfig->rssilimit = 0; // threshold for rssilimiter, negative value! + myconfig->sendcycle = SENDCYCLE; // payload send cycle [seconds/2] + myconfig->wifichancycle = WIFI_CHANNEL_SWITCH_INTERVAL; // wifi channel switch cycle [seconds/100] - cfg.blescantime = + myconfig->blescantime = BLESCANINTERVAL / 10; // BT channel scan cycle [seconds/100], default 1 (= 10ms) - cfg.blescan = 1; // 0=disabled, 1=enabled - cfg.wifiscan = 1; // 0=disabled, 1=enabled - cfg.wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4) - cfg.vendorfilter = VENDORFILTER; // 0=disabled, 1=enabled - cfg.rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) - cfg.monitormode = 0; // 0=disabled, 1=enabled - cfg.payloadmask = PAYLOADMASK; // all payload switched on - cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE] = { - 0}; // init BSEC state for BME680 sensor + myconfig->blescan = 1; // 0=disabled, 1=enabled + myconfig->wifiscan = 1; // 0=disabled, 1=enabled + myconfig->wifiant = 0; // 0=internal, 1=external (for LoPy/LoPy4) + myconfig->vendorfilter = VENDORFILTER; // 0=disabled, 1=enabled + myconfig->rgblum = RGBLUMINOSITY; // RGB Led luminosity (0..100%) + myconfig->monitormode = 0; // 0=disabled, 1=enabled + myconfig->payloadmask = PAYLOADMASK; // payloads as defined in default + myconfig->enscount = COUNT_ENS; // 0=disabled, 1=enabled - strncpy(cfg.version, PROGVERSION, sizeof(cfg.version) - 1); +#ifdef HAS_BME680 + // initial BSEC state for BME680 sensor + myconfig->bsecstate[BSEC_MAX_STATE_BLOB_SIZE] = {0}; +#endif } -void open_storage() { - err = nvs_flash_init(); - if (err == ESP_ERR_NVS_NO_FREE_PAGES) { - // NVS partition was truncated and needs to be erased - // Retry nvs_flash_init - ESP_ERROR_CHECK(nvs_flash_erase()); - err = nvs_flash_init(); - } - ESP_ERROR_CHECK(err); - - // Open - ESP_LOGI(TAG, "Opening NVS"); - err = nvs_open("config", NVS_READWRITE, &my_handle); - if (err != ESP_OK) - ESP_LOGI(TAG, "Error (%d) opening NVS handle", err); - else - ESP_LOGI(TAG, "Done"); -} - -// erase all keys and values in NVRAM -void eraseConfig() { - ESP_LOGI(TAG, "Clearing settings in NVS"); - open_storage(); - if (err == ESP_OK) { - nvs_erase_all(my_handle); - nvs_commit(my_handle); - nvs_close(my_handle); - ESP_LOGI(TAG, "Done"); - } else { - ESP_LOGW(TAG, "NVS erase failed"); - } +// migrate runtime configuration from earlier to current version +static void migrateConfig(void) { + // currently no configuration migration rules are implemented, we reset to + // factory settings instead + eraseConfig(); } // save current configuration from RAM to NVRAM -void saveConfig() { - ESP_LOGI(TAG, "Storing settings in NVS"); - open_storage(); - if (err == ESP_OK) { - int8_t flash8 = 0; - int16_t flash16 = 0; - size_t required_size; - uint8_t bsecstate_buffer[BSEC_MAX_STATE_BLOB_SIZE + 1]; - char storedversion[10]; +void saveConfig(bool erase) { + ESP_LOGI(TAG, "Storing settings to NVRAM..."); - if (nvs_get_blob(my_handle, "bsecstate", bsecstate_buffer, - &required_size) != ESP_OK || - memcmp(bsecstate_buffer, cfg.bsecstate, BSEC_MAX_STATE_BLOB_SIZE + 1) != - 0) - nvs_set_blob(my_handle, "bsecstate", cfg.bsecstate, - BSEC_MAX_STATE_BLOB_SIZE + 1); + nvram.begin(DEVCONFIG, false); - if (nvs_get_str(my_handle, "version", storedversion, &required_size) != - ESP_OK || - strcmp(storedversion, cfg.version) != 0) - nvs_set_str(my_handle, "version", cfg.version); - - 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) - nvs_set_i8(my_handle, "txpower", cfg.txpower); - - if (nvs_get_i8(my_handle, "adrmode", &flash8) != ESP_OK || - flash8 != cfg.adrmode) - nvs_set_i8(my_handle, "adrmode", cfg.adrmode); - - if (nvs_get_i8(my_handle, "screensaver", &flash8) != ESP_OK || - flash8 != cfg.screensaver) - nvs_set_i8(my_handle, "screensaver", cfg.screensaver); - - if (nvs_get_i8(my_handle, "screenon", &flash8) != ESP_OK || - flash8 != cfg.screenon) - nvs_set_i8(my_handle, "screenon", cfg.screenon); - - if (nvs_get_i8(my_handle, "countermode", &flash8) != ESP_OK || - flash8 != cfg.countermode) - nvs_set_i8(my_handle, "countermode", cfg.countermode); - - if (nvs_get_i8(my_handle, "sendcycle", &flash8) != ESP_OK || - flash8 != cfg.sendcycle) - nvs_set_i8(my_handle, "sendcycle", cfg.sendcycle); - - if (nvs_get_i8(my_handle, "wifichancycle", &flash8) != ESP_OK || - flash8 != cfg.wifichancycle) - nvs_set_i8(my_handle, "wifichancycle", cfg.wifichancycle); - - if (nvs_get_i8(my_handle, "blescantime", &flash8) != ESP_OK || - flash8 != cfg.blescantime) - nvs_set_i8(my_handle, "blescantime", cfg.blescantime); - - if (nvs_get_i8(my_handle, "blescanmode", &flash8) != ESP_OK || - flash8 != cfg.blescan) - nvs_set_i8(my_handle, "blescanmode", cfg.blescan); - - if (nvs_get_i8(my_handle, "wifiscanmode", &flash8) != ESP_OK || - flash8 != cfg.wifiscan) - nvs_set_i8(my_handle, "wifiscanmode", cfg.wifiscan); - - if (nvs_get_i8(my_handle, "wifiant", &flash8) != ESP_OK || - flash8 != cfg.wifiant) - nvs_set_i8(my_handle, "wifiant", cfg.wifiant); - - if (nvs_get_i8(my_handle, "vendorfilter", &flash8) != ESP_OK || - flash8 != cfg.vendorfilter) - nvs_set_i8(my_handle, "vendorfilter", cfg.vendorfilter); - - if (nvs_get_i8(my_handle, "rgblum", &flash8) != ESP_OK || - flash8 != cfg.rgblum) - nvs_set_i8(my_handle, "rgblum", cfg.rgblum); - - if (nvs_get_i8(my_handle, "payloadmask", &flash8) != ESP_OK || - flash8 != cfg.payloadmask) - nvs_set_i8(my_handle, "payloadmask", cfg.payloadmask); - - if (nvs_get_i8(my_handle, "monitormode", &flash8) != ESP_OK || - flash8 != cfg.monitormode) - nvs_set_i8(my_handle, "monitormode", cfg.monitormode); - - if (nvs_get_i16(my_handle, "rssilimit", &flash16) != ESP_OK || - flash16 != cfg.rssilimit) - nvs_set_i16(my_handle, "rssilimit", cfg.rssilimit); - - err = nvs_commit(my_handle); - nvs_close(my_handle); - if (err == ESP_OK) { - ESP_LOGI(TAG, "Done"); - } else { - ESP_LOGW(TAG, "NVS config write failed"); - } - } else { - ESP_LOGW(TAG, "Error (%d) opening NVS handle", err); + if (erase) { + ESP_LOGI(TAG, "Resetting device to factory settings"); + nvram.clear(); + defaultConfig(&cfg); } -} -// set and save cfg.version -void migrateVersion() { - snprintf(cfg.version, 10, "%s", PROGVERSION); - ESP_LOGI(TAG, "version set to %s", cfg.version); - saveConfig(); + // Copy device runtime config cfg to byte array, padding it with magicBytes + memcpy(buffer, &cfg, cfgLen); + memcpy(buffer + cfgLen, &cfgMagicBytes, cfgLen2); + + // save byte array to NVRAM, padding with cfg magicbyes + if (nvram.putBytes(DEVCONFIG, buffer, cfgLen + cfgLen2)) + ESP_LOGI(TAG, "Device settings saved"); + else + ESP_LOGE(TAG, "NVRAM Error, device settings not saved"); + + nvram.end(); } // load configuration from NVRAM into RAM and make it current -void loadConfig() { - defaultConfig(); // start with factory settings - ESP_LOGI(TAG, "Reading settings from NVS"); - open_storage(); - if (err != ESP_OK) { - ESP_LOGW(TAG, "Error (%d) opening NVS handle, storing defaults", err); - saveConfig(); - } // saves factory settings to NVRAM - else { - int8_t flash8 = 0; - int16_t flash16 = 0; - size_t required_size; +bool loadConfig() { - // check if configuration stored in NVRAM matches PROGVERSION - if (nvs_get_str(my_handle, "version", NULL, &required_size) == ESP_OK) { - nvs_get_str(my_handle, "version", cfg.version, &required_size); - ESP_LOGI(TAG, "NVRAM settings version = %s", cfg.version); - if (strcmp(cfg.version, PROGVERSION)) { - ESP_LOGI(TAG, "migrating NVRAM settings to new version %s", - PROGVERSION); - nvs_close(my_handle); - migrateVersion(); - } - } else { - ESP_LOGI(TAG, "new version %s, deleting NVRAM settings", PROGVERSION); - nvs_close(my_handle); - eraseConfig(); - migrateVersion(); - } + ESP_LOGI(TAG, "Loading device configuration from NVRAM..."); - // populate pre set defaults with current values from NVRAM - - if (nvs_get_blob(my_handle, "bsecstate", NULL, &required_size) == ESP_OK) { - nvs_get_blob(my_handle, "bsecstate", cfg.bsecstate, &required_size); - ESP_LOGI(TAG, "bsecstate = %d", cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE]); - }; - - if (nvs_get_i8(my_handle, "loradr", &flash8) == ESP_OK) { - cfg.loradr = flash8; - ESP_LOGI(TAG, "loradr = %d", flash8); - } else { - ESP_LOGI(TAG, "loradr set to default %d", cfg.loradr); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "txpower", &flash8) == ESP_OK) { - cfg.txpower = flash8; - ESP_LOGI(TAG, "txpower = %d", flash8); - } else { - ESP_LOGI(TAG, "txpower set to default %d", cfg.txpower); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "adrmode", &flash8) == ESP_OK) { - cfg.adrmode = flash8; - ESP_LOGI(TAG, "adrmode = %d", flash8); - } else { - ESP_LOGI(TAG, "adrmode set to default %d", cfg.adrmode); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "screensaver", &flash8) == ESP_OK) { - cfg.screensaver = flash8; - ESP_LOGI(TAG, "screensaver = %d", flash8); - } else { - ESP_LOGI(TAG, "screensaver set to default %d", cfg.screensaver); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "screenon", &flash8) == ESP_OK) { - cfg.screenon = flash8; - ESP_LOGI(TAG, "screenon = %d", flash8); - } else { - ESP_LOGI(TAG, "screenon set to default %d", cfg.screenon); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "countermode", &flash8) == ESP_OK) { - cfg.countermode = flash8; - ESP_LOGI(TAG, "countermode = %d", flash8); - } else { - ESP_LOGI(TAG, "countermode set to default %d", cfg.countermode); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "sendcycle", &flash8) == ESP_OK) { - cfg.sendcycle = flash8; - ESP_LOGI(TAG, "sendcycle = %d", flash8); - } else { - ESP_LOGI(TAG, "Payload send cycle set to default %d", cfg.sendcycle); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "wifichancycle", &flash8) == ESP_OK) { - cfg.wifichancycle = flash8; - ESP_LOGI(TAG, "wifichancycle = %d", flash8); - } else { - ESP_LOGI(TAG, "WIFI channel cycle set to default %d", cfg.wifichancycle); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "wifiant", &flash8) == ESP_OK) { - cfg.wifiant = flash8; - ESP_LOGI(TAG, "wifiantenna = %d", flash8); - } else { - ESP_LOGI(TAG, "WIFI antenna switch set to default %d", cfg.wifiant); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "vendorfilter", &flash8) == ESP_OK) { - cfg.vendorfilter = flash8; - ESP_LOGI(TAG, "vendorfilter = %d", flash8); - } else { - ESP_LOGI(TAG, "Vendorfilter mode set to default %d", cfg.vendorfilter); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "rgblum", &flash8) == ESP_OK) { - cfg.rgblum = flash8; - ESP_LOGI(TAG, "rgbluminosity = %d", flash8); - } else { - ESP_LOGI(TAG, "RGB luminosity set to default %d", cfg.rgblum); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "blescantime", &flash8) == ESP_OK) { - cfg.blescantime = flash8; - ESP_LOGI(TAG, "blescantime = %d", flash8); - } else { - ESP_LOGI(TAG, "BLEscantime set to default %d", cfg.blescantime); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "blescanmode", &flash8) == ESP_OK) { - cfg.blescan = flash8; - ESP_LOGI(TAG, "BLEscanmode = %d", flash8); - } else { - ESP_LOGI(TAG, "BLEscanmode set to default %d", cfg.blescan); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "wifiscanmode", &flash8) == ESP_OK) { - cfg.wifiscan = flash8; - ESP_LOGI(TAG, "WIFIscanmode = %d", flash8); - } else { - ESP_LOGI(TAG, "WIFIscanmode set to default %d", cfg.wifiscan); - saveConfig(); - } - - if (nvs_get_i16(my_handle, "rssilimit", &flash16) == ESP_OK) { - cfg.rssilimit = flash16; - ESP_LOGI(TAG, "rssilimit = %d", flash16); - } else { - ESP_LOGI(TAG, "rssilimit set to default %d", cfg.rssilimit); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "payloadmask", &flash8) == ESP_OK) { - cfg.payloadmask = flash8; - ESP_LOGI(TAG, "payloadmask = %hhu", flash8); - } else { - ESP_LOGI(TAG, "payloadmask set to default %hhu", cfg.payloadmask); - saveConfig(); - } - - if (nvs_get_i8(my_handle, "monitormode", &flash8) == ESP_OK) { - cfg.monitormode = flash8; - ESP_LOGI(TAG, "Monitor mode = %d", flash8); - } else { - ESP_LOGI(TAG, "Monitor mode set to default %d", cfg.monitormode); - saveConfig(); - } - - nvs_close(my_handle); - ESP_LOGI(TAG, "Done"); + if (!nvram.begin(DEVCONFIG, true)) { + ESP_LOGI(TAG, "NVRAM initialized, device starts with factory settings"); + eraseConfig(); } -} // loadConfig() + + // simple check that runtime config data matches + // if (nvram.getBytesLength(DEVCONFIG) != (cfgLen + cfgLen2)) { + // ESP_LOGE(TAG, "Configuration invalid"); + // return false; + //} + + // load device runtime config from nvram and copy it to byte array + nvram.getBytes(DEVCONFIG, buffer, cfgLen + cfgLen2); + nvram.end(); + + // validate loaded configuration by checking magic bytes at end of array + // if (memcmp(buffer + cfgLen, &cfgMagicBytes, cfgLen2) != 0) { + // ESP_LOGW(TAG, "No configuration found"); + // return false; + //} + + // copy loaded configuration into runtime cfg struct + memcpy(&cfg, buffer, cfgLen); + ESP_LOGI(TAG, "Runtime configuration v%s loaded", cfg.version); + + // check if config version matches current firmware version + switch (version_compare(PROGVERSION, cfg.version)) { + case -1: // device configuration belongs to newer than current firmware + ESP_LOGE(TAG, "Incompatible device configuration"); + return false; + case 1: // device configuration belongs to older than current firmware + ESP_LOGW(TAG, "Device was updated, attempt to migrate configuration"); + migrateConfig(); + return true; + default: // device configuration version matches current firmware version + return true; + } +} + +// helper function to convert strings into lower case +bool comp(char s1, char s2) { return (tolower(s1) < tolower(s2)); } + +// helper function to lexicographically compare two versions. Returns 1 if v2 +// is smaller, -1 if v1 is smaller, 0 if equal +int version_compare(const String v1, const String v2) { + + if (v1 == v2) + return 0; + + const char *a1 = v1.c_str(), *a2 = v2.c_str(); + + if (std::lexicographical_compare(a1, a1 + strlen(a1), a2, a2 + strlen(a2), + comp)) + return -1; + else + return 1; +} + +void eraseConfig(void) { saveConfig(true); } \ No newline at end of file diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 96521a61..c57a0863 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -7,13 +7,13 @@ // Local logging tag static const char TAG[] = __FILE__; -Ticker housekeeper; +Ticker cyclicTimer; #if (HAS_SDS011) extern boolean isSDS011Active; #endif -void housekeeping() { +void setCyclicIRQ() { xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL); } @@ -150,7 +150,7 @@ uint32_t getFreeRAM() { void reset_counters() { #if ((WIFICOUNTER) || (BLECOUNTER)) - macs.clear(); // clear all macs container + macs.clear(); // clear all macs container macs_wifi = 0; macs_ble = 0; #ifdef HAS_DISPLAY diff --git a/src/display.cpp b/src/display.cpp index aa2289ee..c8d88694 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -265,11 +265,10 @@ void dp_drawPage(time_t t, bool nextpage) { else dp_printf("WIFI:off"); if (cfg.blescan) -#if (!COUNT_ENS) - dp_printf("BLTH:%-5d", macs_ble); -#else + if (!cfg.enscount) + dp_printf("BLTH:%-5d", macs_ble); + else dp_printf(" CWA:%-5d", cwa_report()); -#endif else dp_printf(" BLTH:off"); #elif ((WIFICOUNTER) && (!BLECOUNTER)) @@ -280,10 +279,10 @@ void dp_drawPage(time_t t, bool nextpage) { #elif ((!WIFICOUNTER) && (BLECOUNTER)) if (cfg.blescan) { dp_printf("BLTH:%-5d", macs_ble); -#if (COUNT_ENS) - dp_printf("(CWA:%d)", cwa_report()); -#endif - } else + if (cfg.enscount) + dp_printf("(CWA:%d)", cwa_report()); + } + else dp_printf("BLTH:off"); #else dp_printf("Sniffer disabled"); diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 5589f4b2..75206e59 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -12,7 +12,10 @@ static const char TAG[] = __FILE__; // thus precision is only +/- 1 second TinyGPSPlus gps; -TinyGPSCustom gpstime(gps, "GPZDA", 1); // field 1 = UTC time +TinyGPSCustom gpstime(gps, "GPZDA", 1); // field 1 = UTC time (hhmmss.ss) +TinyGPSCustom gpsday(gps, "GPZDA", 2); // field 2 = day (01..31) +TinyGPSCustom gpsmonth(gps, "GPZDA", 3); // field 3 = month (01..12) +TinyGPSCustom gpsyear(gps, "GPZDA", 4); // field 4 = year (4-digit) static const String ZDA_Request = "$EIGPQ,ZDA*39\r\n"; TaskHandle_t GpsTask; @@ -93,47 +96,49 @@ bool gps_hasfix() { gps.altitude.age() < 4000); } -// function to fetch current time from struct; note: this is costly +// function to poll current time from GPS data; note: this is costly time_t get_gpstime(uint16_t *msec) { - time_t time_sec = 0; - // poll NMEA ZDA sentence #ifdef GPS_SERIAL GPS_Serial.print(ZDA_Request); // wait for gps NMEA answer - vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL)); + // vTaskDelay(tx_Ticks(NMEA_FRAME_SIZE, GPS_SERIAL)); #elif defined GPS_I2C Wire.print(ZDA_Request); #endif - // did we get a current time? - if (gpstime.isUpdated() && gpstime.isValid()) { + // did we get a current date & time? + if (gpstime.isValid() && gpsday.isValid()) { + time_t t = 0; tmElements_t tm; - - String rawtime = gpstime.value(); - uint32_t time_bcd = rawtime.toFloat() * 100; 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 + uint32_t zdatime = atof(gpstime.value()); - ESP_LOGD(TAG, "time [bcd]: %u", time_bcd); + // convert time to maketime format and make time + tm.Second = zdatime % 100; // second + tm.Minute = (zdatime / 100) % 100; // minute + tm.Hour = zdatime / 10000; // hour + tm.Day = atoi(gpsday.value()); // day + tm.Month = atoi(gpsmonth.value()); // month + tm.Year = CalendarYrToTm(atoi(gpsyear.value())); // year offset from 1970 + t = makeTime(tm); - tm.Second = (time_bcd / 100) % 100; // second - tm.Minute = (time_bcd / 10000) % 100; // minute - tm.Hour = time_bcd / 1000000; // hour - tm.Day = gps.date.day(); // day - tm.Month = gps.date.month(); // month - tm.Year = year; // year + // ESP_LOGD(TAG, "GPS time/date = %2d:%2d:%2d / %2d.%2d.%2d", tm.Hour, + // tm.Minute, tm.Second, tm.Day, tm.Month, tm.Year + 1970); - // add protocol delay to time with millisecond precision - time_sec = makeTime(tm) + delay_ms / 1000; - *msec = (delay_ms % 1000) ? delay_ms % 1000 : 0; + // add protocol delay with millisecond precision + t += delay_ms / 1000 - 1; // whole seconds + *msec = delay_ms % 1000; // fractional seconds + + return t; } - return timeIsValid(time_sec); + ESP_LOGD(TAG, "no valid GPS time"); + + return 0; } // get_gpstime() @@ -163,15 +168,16 @@ void gps_loop(void *pvParameters) { } #endif - // if time hasn't been synchronised yet, and we have a valid GPS time, - // update time from GPS. - if (timeSource == _unsynced && gpstime.isUpdated() && gpstime.isValid()) { + // (only) while device time is not set or unsynched, and we have a valid + // GPS time, we trigger a device time update to poll time from GPS + if (timeSource == _unsynced && gpstime.isUpdated()) { + now(); calibrateTime(); } } // if - // show NMEA data in verbose mode, useful for debugging GPS, bu tvery noisy + // show NMEA data in verbose mode, useful only for debugging GPS, very noisy // ESP_LOGV(TAG, "GPS NMEA data: passed %u / failed: %u / with fix: %u", // gps.passedChecksum(), gps.failedChecksum(), // gps.sentencesWithFix()); diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index 528a8187..a169f4b1 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -84,7 +84,7 @@ void irqHandler(void *pvParameters) { } // do we have a power event? -#if (HAS_PMU) +#ifdef HAS_PMU if (InterruptStatus & PMU_IRQ) { AXP192_powerevent_IRQ(); InterruptStatus &= ~PMU_IRQ; diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 5b673074..34cc4a0b 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -527,22 +527,16 @@ void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, // rcommand received -> call interpreter case RCMDPORT: rcommand(pMsg, nMsg); + break; // timeserver answer -> call timesync processor #if (TIME_SYNC_LORASERVER) case TIMEPORT: // get and store gwtime from payload timesync_serverAnswer(const_cast(pMsg), nMsg); + break; #endif - // decode any piggybacked downlink MAC commands if we want to print those - default: -#if (VERBOSE) - if (LMIC.dataBeg > 1) - mac_decode(LMIC.frame, LMIC.dataBeg - 1, true); -#endif // VERBOSE - - break; } // switch } diff --git a/src/main.cpp b/src/main.cpp index 9004dbb3..bcecd934 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -55,17 +55,24 @@ So don't do it if you do not own a digital oscilloscope. // Interrupt routines ------------------------------------------------------------------------------- +irqHandlerTask (Core 1), see irqhandler.cpp + 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) +DisplayIRQ -> esp32 timer 0 +ButtonIRQ -> external gpio +PMUIRQ -> PMU chip gpio fired by software (Ticker.h) -TIMESYNC_IRQ -> timeSync() -> irqHandlerTask (Core 1) -CYCLIC_IRQ -> housekeeping() -> irqHandlerTask (Core 1) -SENDCYCLE_IRQ -> sendcycle() -> irqHandlerTask (Core 1) -BME_IRQ -> bmecycle() -> irqHandlerTask (Core 1) +TIMESYNC_IRQ -> setTimeSyncIRQ() +CYCLIC_IRQ -> setCyclicIRQ() +SENDCYCLE_IRQ -> setSendIRQ() +BME_IRQ -> setBMEIRQ() +MQTT_IRQ -> setMqttIRQ() + +ClockTask (Core 1), see timekeeper.cpp + +fired by hardware +CLOCKIRQ -> esp32 timer 1 // External RTC timer (if present) @@ -196,7 +203,7 @@ void setup() { #endif // read (and initialize on first run) runtime settings from NVRAM - loadConfig(); // includes initialize if necessary + assert(loadConfig()); // includes initialize if necessary // now that we are powered, we scan i2c bus for devices i2c_scan(); @@ -478,8 +485,8 @@ void setup() { #endif // HAS_BUTTON // cyclic function interrupts - sendcycler.attach(cfg.sendcycle * 2, sendcycle); - housekeeper.attach(HOMECYCLE, housekeeping); + sendTimer.attach(cfg.sendcycle * 2, setSendIRQ); + cyclicTimer.attach(HOMECYCLE, setCyclicIRQ); #if (TIME_SYNC_INTERVAL) diff --git a/src/mqttclient.cpp b/src/mqttclient.cpp index e9333c8a..9eb2567f 100644 --- a/src/mqttclient.cpp +++ b/src/mqttclient.cpp @@ -33,7 +33,7 @@ esp_err_t mqtt_init(void) { SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE); ESP_LOGI(TAG, "Starting MQTTloop..."); - mqttTimer.attach(MQTT_KEEPALIVE, mqtt_irq); + mqttTimer.attach(MQTT_KEEPALIVE, setMqttIRQ); xTaskCreate(mqtt_client_task, "mqttloop", 4096, (void *)NULL, 1, &mqttTask); return ESP_OK; @@ -183,6 +183,6 @@ void mqtt_loop(void) { } void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); } -void mqtt_irq(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); } +void setMqttIRQ(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); } #endif // HAS_MQTT \ No newline at end of file diff --git a/src/ota.cpp b/src/ota.cpp index 95d780f8..c256559e 100644 --- a/src/ota.cpp +++ b/src/ota.cpp @@ -328,22 +328,4 @@ void show_progress(unsigned long current, unsigned long size) { #endif } -// helper function to convert strings into lower case -bool comp(char s1, char s2) { return tolower(s1) < tolower(s2); } - -// helper function to lexicographically compare two versions. Returns 1 if v2 is -// smaller, -1 if v1 is smaller, 0 if equal -int version_compare(const String v1, const String v2) { - - if (v1 == v2) - return 0; - - const char *a1 = v1.c_str(), *a2 = v2.c_str(); - - if (lexicographical_compare(a1, a1 + strlen(a1), a2, a2 + strlen(a2), comp)) - return -1; - else - return 1; -} - #endif // USE_OTA \ No newline at end of file diff --git a/src/paxcounter_orig.conf b/src/paxcounter_orig.conf index a174d819..06a1e624 100644 --- a/src/paxcounter_orig.conf +++ b/src/paxcounter_orig.conf @@ -12,7 +12,7 @@ // Payload send cycle and encoding #define SENDCYCLE 30 // payload send cycle [seconds/2], 0 .. 255 #define PAYLOAD_ENCODER 2 // payload encoder: 1=Plain, 2=Packed, 3=Cayenne LPP dynamic, 4=Cayenne LPP packed -#define COUNTERMODE 1 // 0=cyclic, 1=cumulative, 2=cyclic confirmed +#define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed // Set this to include BLE counting and vendor filter functions, or to switch off WIFI counting #define VENDORFILTER 0 // set to 0 if you want to scan all devices, not filtering smartphone OUIs diff --git a/src/payload.cpp b/src/payload.cpp index 7517e9d5..112a544d 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -181,14 +181,7 @@ void PayloadConvert::addConfig(configData_t value) { value.blescan ? true : false, value.wifiant ? true : false, value.vendorfilter ? true : false, value.monitormode ? true : false); - writeBitmap(value.payloadmask && GPS_DATA ? true : false, - value.payloadmask && ALARM_DATA ? true : false, - value.payloadmask && MEMS_DATA ? true : false, - value.payloadmask && COUNT_DATA ? true : false, - value.payloadmask && SENSOR1_DATA ? true : false, - value.payloadmask && SENSOR2_DATA ? true : false, - value.payloadmask && SENSOR3_DATA ? true : false, - value.payloadmask && BATT_DATA ? true : false); + writeUint8(value.payloadmask); writeVersion(value.version); } diff --git a/src/platformio_orig.ini b/src/platformio_orig.ini new file mode 100644 index 00000000..80087854 --- /dev/null +++ b/src/platformio_orig.ini @@ -0,0 +1,135 @@ +; PlatformIO Project Configuration File +; NOTE: PlatformIO v4 is needed! +; +; Please visit documentation for the other options and examples +; http://docs.platformio.org/page/projectconf.html + + +; ---> SELECT THE TARGET PLATFORM HERE! <--- +[board] +halfile = generic.h +;halfile = ebox.h +;halfile = eboxtube.h +;halfile = ecopower.h +;halfile = heltec.h +;halfile = heltecv2.h +;halfile = ttgov1.h +;halfile = ttgov2.h +;halfile = ttgov21old.h +;halfile = ttgov21new.h +;halfile = ttgofox.h +;halfile = ttgobeam.h +;halfile = ttgobeam10.h +;halfile = fipy.h +;halfile = lopy.h +;halfile = lopy4.h +;halfile = lolin32litelora.h +;halfile = lolin32lora.h +;halfile = lolin32lite.h +;halfile = wemos32oled.h +;halfile = wemos32matrix.h +;halfile = octopus32.h +;halfile = tinypico.h +;halfile = tinypicomatrix.h +;halfile = m5core.h +;halfile = m5fire.h +;halfile = olimexpoeiso.h + +[platformio] +; upload firmware to board with usb cable +default_envs = usb +; upload firmware to a jfrog bintray repository +;default_envs = ota +; use latest versions of libraries +;default_envs = dev +description = Paxcounter is a device for metering passenger flows in realtime. It counts how many mobile devices are around. + +[common] +; for release_version use max. 10 chars total, use any decimal format like "a.b.c" +release_version = 2.0.16 +; 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 +extra_scripts = pre:build.py +otakeyfile = ota.conf +lorakeyfile = loraconf.h +lmicconfigfile = lmic_config.h +platform_espressif32 = espressif32@2.0.0 +monitor_speed = 115200 +upload_speed = 115200 ; set by build.py and taken from hal file +display_library = ; set by build.py and taken from hal file +lib_deps_lora = + mcci-catena/MCCI LoRaWAN LMIC library @ ^3.2.0 +lib_deps_display = + bitbank2/OneBitDisplay @ 1.7.2 + ricmoo/QRCode @ ^0.0.1 + bodmer/TFT_eSPI @ ^2.2.20 +lib_deps_ledmatrix = + seeed-studio/Ultrathin_LED_Matrix @ ^1.0.0 +lib_deps_rgbled = + roboticsbrno/SmartLeds @ ^1.2.1 +lib_deps_gps = + mikalhart/TinyGPSPlus @ ^1.0.2 +lib_deps_sensors = + adafruit/Adafruit Unified Sensor @ ^1.1.4 + adafruit/Adafruit BME280 Library @ ^2.1.1 + adafruit/Adafruit BMP085 Library @ ^1.1.0 + boschsensortec/BSEC Software Library @ 1.5.1474 + https://github.com/ricki-z/SDS011.git +lib_deps_basic = + bblanchon/ArduinoJson @ <6 + jchristensen/Timezone @ ^1.2.4 + makuna/RTC @ ^2.3.5 + spacehuhn/SimpleButton + lewisxhe/AXP202X_Library @ ^1.1.2 + geeksville/esp32-micro-sdcard @ ^0.1.1 + 256dpi/MQTT @ ^2.4.7 +lib_deps_all = + ${common.lib_deps_basic} + ${common.lib_deps_lora} + ${common.lib_deps_display} + ${common.lib_deps_rgbled} + ${common.lib_deps_gps} + ${common.lib_deps_sensors} + ${common.lib_deps_ledmatrix} +build_flags_basic = + -include "src/hal/${board.halfile}" + -include "src/paxcounter.conf" + -w + '-DCORE_DEBUG_LEVEL=${common.debug_level}' + '-DLOG_LOCAL_LEVEL=${common.debug_level}' + '-DPROGVERSION="${common.release_version}"' +build_flags_sensors = + -Llib/Bosch-BSEC/src/esp32/ + -lalgobsec +build_flags_all = + ${common.build_flags_basic} + ${common.build_flags_sensors} + -mfix-esp32-psram-cache-issue + +[env] +lib_ldf_mode = deep ; #632 Fixes compiler error with OneBitDisplay library +framework = arduino +board = esp32dev +board_build.partitions = min_spiffs.csv +upload_speed = ${common.upload_speed} +;upload_port = COM8 +platform = ${common.platform_espressif32} +lib_deps = ${common.lib_deps_all} +build_flags = ${common.build_flags_all} +upload_protocol = ${common.upload_protocol} +extra_scripts = ${common.extra_scripts} +monitor_speed = ${common.monitor_speed} +monitor_filters = time, esp32_exception_decoder, default + +[env:ota] +upload_protocol = custom + +[env:usb] +upload_protocol = esptool + +[env:dev] +upload_protocol = esptool +build_type = debug +platform = https://github.com/platformio/platform-espressif32.git#develop +platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git diff --git a/src/rcommand.cpp b/src/rcommand.cpp index adb61a49..75899eca 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -48,8 +48,8 @@ void set_rssi(uint8_t val[]) { void set_sendcycle(uint8_t val[]) { cfg.sendcycle = val[0]; - // update send cycle interrupt [seconds - sendcycler.attach(cfg.sendcycle * 2, sendcycle); + // update send cycle interrupt [seconds / 2] + sendTimer.attach(cfg.sendcycle * 2, setSendIRQ); ESP_LOGI(TAG, "Remote command: set send cycle to %d seconds", cfg.sendcycle * 2); } @@ -329,8 +329,8 @@ void get_time(uint8_t val[]) { }; void set_time(uint8_t val[]) { - ESP_LOGI(TAG, "Timesync requested by timeserver"); - timeSync(); + ESP_LOGI(TAG, "Remote command: timesync requested"); + setTimeSyncIRQ(); }; void set_flush(uint8_t val[]) { @@ -339,6 +339,15 @@ void set_flush(uint8_t val[]) { // used to open receive window on LoRaWAN class a nodes }; +void set_enscount(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: set ENS_COUNT to %s", val[0] ? "on" : "off"); + cfg.enscount = val[0] ? 1 : 0; + if (val[0]) + cfg.payloadmask |= SENSOR1_DATA; + else + cfg.payloadmask &= ~SENSOR1_DATA; +} + // assign previously defined functions to set of numeric remote commands // format: opcode, function, #bytes params, // flag (true = do make settings persistent / false = don't) @@ -355,11 +364,11 @@ static const cmd_t table[] = { {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, {0x13, set_sensor, 2, true}, {0x14, set_payloadmask, 1, true}, {0x15, set_bme, 1, true}, {0x16, set_batt, 1, true}, - {0x17, set_wifiscan, 1, true}, {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}}; + {0x17, set_wifiscan, 1, true}, {0x18, set_enscount, 1, true}, + {0x80, get_config, 0, false}, {0x81, get_status, 0, false}, + {0x83, get_batt, 0, false}, {0x84, get_gps, 0, false}, + {0x85, get_bme, 0, false}, {0x86, get_time, 0, false}, + {0x87, set_time, 0, false}, {0x99, set_flush, 0, false}}; static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table diff --git a/src/senddata.cpp b/src/senddata.cpp index a9ca74ea..1cc09503 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -1,9 +1,9 @@ // Basic Config #include "senddata.h" -Ticker sendcycler; +Ticker sendTimer; -void sendcycle() { +void setSendIRQ() { xTaskNotifyFromISR(irqHandlerTask, SENDCYCLE_IRQ, eSetBits, NULL); } @@ -154,7 +154,8 @@ void sendData() { payload.addSensor(sensor_read(1)); SendPayload(SENSOR1PORT, prio_normal); #if (COUNT_ENS) - cwa_clear(); + if (cfg.countermode != 1) + cwa_clear(); #endif break; #endif diff --git a/src/sensor.cpp b/src/sensor.cpp index 3b5f9e2d..bb34030e 100644 --- a/src/sensor.cpp +++ b/src/sensor.cpp @@ -57,7 +57,8 @@ uint8_t *sensor_read(uint8_t sensor) { // insert user specific sensor data frames here // note: Sensor1 fields are used for ENS count, if ENS detection enabled #if (COUNT_ENS) - payload.addCount(cwa_report(), MAC_SNIFF_BLE_CWA); + if (cfg.enscount) + payload.addCount(cwa_report(), MAC_SNIFF_BLE_CWA); #else buf[0] = length; buf[1] = 0x01; @@ -65,7 +66,6 @@ uint8_t *sensor_read(uint8_t sensor) { buf[3] = 0x03; #endif break; - case 2: buf[0] = length; diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 21513a05..051f77c3 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -23,7 +23,7 @@ HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS) Ticker timesyncer; -void timeSync() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); } +void setTimeSyncIRQ() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); } void calibrateTime(void) { ESP_LOGD(TAG, "[%0.3f] calibrateTime, timeSource == %d", millis() / 1000.0, @@ -85,7 +85,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000)); } - ESP_LOGD(TAG, "[%0.3f] UTC time: %d.%03d sec", millis() / 1000.0, + ESP_LOGI(TAG, "[%0.3f] UTC 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 @@ -104,12 +104,12 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, 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", + timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ); + ESP_LOGD(TAG, "[%0.3f] Timesync finished, time was set | source: %c", millis() / 1000.0, timeSetSymbols[mytimesource]); } else { - timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); - ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c", + timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, setTimeSyncIRQ); + ESP_LOGD(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c", millis() / 1000.0, timeSetSymbols[mytimesource]); } } @@ -167,8 +167,8 @@ void timepulse_start(void) { #endif // start cyclic time sync - timeSync(); // init systime by RTC or GPS or LORA - timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); + setTimeSyncIRQ(); // init systime by RTC or GPS or LORA + timesyncer.attach(TIME_SYNC_INTERVAL * 60, setTimeSyncIRQ); } // interrupt service routine triggered by either pps or esp32 hardware timer diff --git a/src/timesync.cpp b/src/timesync.cpp index 088ff59f..b79aa110 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -29,7 +29,7 @@ static uint32_t timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps]; static TaskHandle_t timeSyncProcTask; // create task for timeserver handshake processing, called from main.cpp -void timesync_init() { +void timesync_init(void) { xTaskCreatePinnedToCore(timesync_processReq, // task function "timesync_proc", // name of task 2048, // stack size of task @@ -69,8 +69,12 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { // wait for kickoff ulTaskNotifyTake(pdFALSE, portMAX_DELAY); + + // initialize flag and counters timeSyncPending = true; time_offset_ms = sample_idx = 0; + if (++time_sync_seqNo > TIME_SYNC_MAX_SEQNO) + time_sync_seqNo = 0; // wait until we are joined if we are not while (!LMIC.devaddr) { @@ -81,7 +85,7 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { // send timesync request -#if (TIME_SYNC_LORASERVER) // aks user's timeserver (for LoRAWAN < 1.0.3) +#if (TIME_SYNC_LORASERVER) // ask user's timeserver (for LoRAWAN < 1.0.3) payload.reset(); payload.addByte(time_sync_seqNo); SendPayload(TIMEPORT, prio_high); @@ -111,9 +115,7 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { timesync_timestamp[sample_idx][timesync_tx]; #endif - // increment sample_idx and time_sync_seqNo, keeping it in range - if (++time_sync_seqNo > TIME_SYNC_MAX_SEQNO) - time_sync_seqNo = 0; + // increment sample index sample_idx++; // if we are not in last cycle, pause until next cycle @@ -146,15 +148,17 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) { // send timesync end char to show timesync was successful payload.reset(); payload.addByte(TIME_SYNC_END_FLAG); - SendPayload(RCMDPORT, prio_high); + SendPayload(TIMEPORT, prio_high); goto Finish; Fail: // set retry timer - timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); + timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, setTimeSyncIRQ); + // intentionally fallthrough to Finish here Finish: // end of time critical section: release app irq lock + timeSyncPending = false; unmask_user_IRQ(); } // infinite while(1) @@ -179,6 +183,7 @@ void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag) { // mask application irq to ensure accurate timing mask_user_IRQ(); + // return code: 0 = failed / 1 = success int rc = 0; // cast back void parameter to a pointer uint8_t *p = (uint8_t *)pUserData, rcv_seqNo = *p;