Merge branch 'master' of github.com:cyberman54/ESP32-Paxcounter

This commit is contained in:
Oliver Seiler 2020-10-29 17:54:44 +13:00
commit b7141bd1c1
33 changed files with 501 additions and 537 deletions

View File

@ -66,7 +66,7 @@ Depending on board hardware following features are supported:
- SD-card (see section SD-card here) for logging pax data
- Ethernet interface for MQTT communication via TCP/IP
Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).<br>
Target platform must be selected in `platformio.ini`.<br>
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.<br>
Some <b>3D printable cases</b> can be found (and, if wanted so, ordered) on Thingiverse, see
@ -83,12 +83,16 @@ By default bluetooth sniffing not installed (#define *BLECOUNTER* 0 in paxcounte
# Preparing
## Install Platformio
Install <A HREF="https://platformio.org/">PlatformIO IDE for embedded development</A> 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`.
@ -171,7 +175,7 @@ by pressing the button of the device.
# Sensors and Peripherals
You can add up to 3 user defined sensors. Insert sensor's payload scheme in [*sensor.cpp*](src/sensor.cpp). Bosch BMP180 / 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. Furthermore, SDS011, 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.
You can add up to 3 user defined sensors. Insert sensor's payload scheme in [*sensor.cpp*](src/sensor.cpp). Bosch BMP180 / BME280 / BME680 environment sensors are supported. Enable flag *lib_deps_sensors* for your board in `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. Furthermore, SDS011, 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 0x14 sent to Port 2.
@ -198,16 +202,13 @@ Paxcounter can keep it's time-of-day synced with an external time source. Set *#
Paxcounter can be used to sync a wall clock which has a DCF77 or IF482 time telegram input. Set *#define HAS_IF482* or *#define HAS_DCF77* in board's hal file to setup clock controller. Use case of this function is to integrate paxcounter and clock. Accurary of the synthetic DCF77 signal depends on accuracy of on board's time base, see above.
# mobile PaxCounter via https://opensensemap.org/
# Mobile PaxCounter using <A HREF="https://opensensemap.org/">openSenseMap</A>
This describes how to set up a mobile PaxCounter:
Follow all steps so far for preparing the device, use the packed payload format. In [paxcounter.conf](src/paxcounter.conf) set PAYLOAD_OPENSENSEBOX to 1. Register a new sensbox on https://opensensemap.org/.
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"}]
This describes how to set up a mobile PaxCounter:<br> Follow all steps so far for preparing the device, selecting the packed payload format. In `paxcounter.conf` set PAYLOAD_OPENSENSEBOX to 1. Register a new sensebox on https://opensensemap.org/. In the sensor configuration select "TheThingsNetwork" and set decoding profile to "LoRa serialization". Enter your TTN Application and Device ID. Setup decoding option using `[{"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 <A HREF="https://linux-fuer-wi.blogspot.com/2020/10/suche-die-zahl-64879.html">gives an indication</A> how many people staying in proximity are using Apps for tracing COVID-19 exposures, e.g. in Germany the "Corona Warn App". To achive best results with this funcion, use following settings in `paxcounter.conf`:
#define COUNT_ENS 1 // enable ENS monitoring function
#define VENDORFILTER 0 // disable OUI filter (scans ALL device MACs)
@ -258,7 +259,7 @@ If you want to change this please look into src/sdcard.cpp and include/sdcard.h.
# Payload format
You can select different payload formats in [paxcounter.conf](src/paxcounter.conf#L12):
You can select different payload formats in `paxcounter.conf`:
- ***Plain*** uses big endian format and generates json fields, e.g. useful for TTN console
@ -353,6 +354,10 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering.
bytes 1-4: board's local time/date in UNIX epoch (number of seconds that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap seconds)
**Ports #10, #11, #12:** User sensor data
Format is specified by user in function `sensor_read(uint8_t sensor)`, see `src/sensor.cpp`. Port #10 is also used for ENS counter (2 bytes = 16 bit), if ENS is compiled AND ENS data transfer is enabled
# Remote control
The device listenes for remote control commands on LoRaWAN Port 2. Multiple commands per downlink are possible by concatenating them.
@ -505,6 +510,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
Device answers with it's current configuration on Port 3.

View File

@ -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);

View File

@ -1,11 +1,12 @@
#ifndef _CONFIGMANAGER_H
#define _CONFIGMANAGER_H
#include <nvs.h>
#include <nvs_flash.h>
#include "globals.h"
#include <Preferences.h>
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

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -6,17 +6,15 @@
#include "globals.h"
#include "led.h"
#include "display.h"
#include "configmanager.h"
#include <Update.h>
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <BintrayClient.h>
#include <string>
#include <algorithm>
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);

View File

@ -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_

View File

@ -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);

View File

@ -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

View File

@ -89,6 +89,11 @@ function Decoder(bytes, port) {
}
}
if (port === 10) {
// ENS count
return decode(bytes, [uint16], ['ens']);
}
}
@ -257,7 +262,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;

View File

@ -82,5 +82,13 @@ function Decoder(bytes, port) {
decoded.timestatus = bytes[i++];
}
}
if (port === 10) {
var i = 0;
if (bytes.length >= 2) {
decoded.ens = (bytes[i++] << 8) | bytes[i++];
}
}
return decoded;
}

View File

@ -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",

View File

@ -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("."));

View File

@ -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)
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:

View File

@ -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()

View File

@ -4,362 +4,157 @@
#include "configmanager.h"
// Local logging tag
static const char TAG[] = "flash";
nvs_handle my_handle;
esp_err_t err;
static const char TAG[] = __FILE__;
// default settings for device data to be sent
#define PAYLOADMASK \
((GPS_DATA | ALARM_DATA | MEMS_DATA | COUNT_DATA | \
SENSOR1_DATA | SENSOR2_DATA | SENSOR3_DATA) & \
((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.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 =
// namespace for device runtime preferences
#define DEVCONFIG "paxcntcfg"
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);
ESP_LOGI(TAG, "Loading device configuration from NVRAM...");
if (!nvram.begin(DEVCONFIG, true)) {
ESP_LOGI(TAG, "NVRAM initialized, device starts with factory settings");
eraseConfig();
migrateVersion();
}
// populate pre set defaults with current values from NVRAM
// simple check that runtime config data matches
// if (nvram.getBytesLength(DEVCONFIG) != (cfgLen + cfgLen2)) {
// ESP_LOGE(TAG, "Configuration invalid");
// return false;
//}
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]);
};
// load device runtime config from nvram and copy it to byte array
nvram.getBytes(DEVCONFIG, buffer, cfgLen + cfgLen2);
nvram.end();
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();
// 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;
}
}
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();
// 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;
}
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");
}
} // loadConfig()
void eraseConfig(void) { saveConfig(true); }

View File

@ -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);
}

View File

@ -265,11 +265,10 @@ void dp_drawPage(time_t t, bool nextpage) {
else
dp_printf("WIFI:off");
if (cfg.blescan)
#if (!COUNT_ENS)
if (!cfg.enscount)
dp_printf("BLTH:%-5d", macs_ble);
#else
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)
if (cfg.enscount)
dp_printf("(CWA:%d)", cwa_report());
#endif
} else
}
else
dp_printf("BLTH:off");
#else
dp_printf("Sniffer disabled");

View File

@ -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());

View File

@ -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;

View File

@ -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<uint8_t *>(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
}

View File

@ -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();
@ -302,7 +309,7 @@ void setup() {
ESP_LOGI(TAG, "Starting GPS Feed...");
xTaskCreatePinnedToCore(gps_loop, // task function
"gpsloop", // name of task
2048, // stack size of task
4096, // stack size of task
(void *)1, // parameter of the task
1, // priority of the task
&GpsTask, // task handle
@ -478,8 +485,8 @@ void setup() {
#endif // HAS_BUTTON
// cyclic function interrupts
sendcycler.attach(SENDCYCLE * 2, sendcycle);
housekeeper.attach(HOMECYCLE, housekeeping);
sendTimer.attach(cfg.sendcycle * 2, setSendIRQ);
cyclicTimer.attach(HOMECYCLE, setCyclicIRQ);
#if (TIME_SYNC_INTERVAL)

View File

@ -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;
@ -69,6 +69,8 @@ int mqtt_connect(const char *my_host, const uint16_t my_port) {
ESP_LOGW(TAG, "MQTT server not responding, retrying later");
return -1;
}
return 0;
}
void NetworkEvent(WiFiEvent_t event) {
@ -122,7 +124,7 @@ void mqtt_client_task(void *param) {
if (mqttClient.connected()) {
char buffer[PAYLOAD_BUFFER_SIZE + 3];
snprintf(buffer, msg.MessageSize + 3, "%s/%s", msg.MessagePort,
snprintf(buffer, msg.MessageSize + 3, "%s/%u", msg.MessagePort,
msg.Message);
if (mqttClient.publish(MQTT_OUTTOPIC, buffer)) {
@ -183,6 +185,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

View File

@ -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

View File

@ -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

View File

@ -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);
}

135
src/platformio_orig.ini Normal file
View File

@ -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

View File

@ -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

View File

@ -1,9 +1,9 @@
// Basic Config
#include "senddata.h"
Ticker sendcycler;
Ticker sendTimer;
void sendcycle() {
void setSendIRQ() {
xTaskNotifyFromISR(irqHandlerTask, SENDCYCLE_IRQ, eSetBits, NULL);
}
@ -154,6 +154,7 @@ void sendData() {
payload.addSensor(sensor_read(1));
SendPayload(SENSOR1PORT, prio_normal);
#if (COUNT_ENS)
if (cfg.countermode != 1)
cwa_clear();
#endif
break;

View File

@ -57,6 +57,7 @@ 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)
if (cfg.enscount)
payload.addCount(cwa_report(), MAC_SNIFF_BLE_CWA);
#else
buf[0] = length;
@ -65,7 +66,6 @@ uint8_t *sensor_read(uint8_t sensor) {
buf[3] = 0x03;
#endif
break;
case 2:
buf[0] = length;

View File

@ -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

View File

@ -29,10 +29,10 @@ 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
4096, // stack size of task
(void *)1, // task parameter
3, // priority of the task
&timeSyncProcTask, // task handle
@ -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;