Merge branch 'master' of github.com:cyberman54/ESP32-Paxcounter
This commit is contained in:
commit
b7141bd1c1
38
README.md
38
README.md
@ -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
|
||||
|
||||
@ -351,7 +352,11 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering.
|
||||
|
||||
**Port #9:** Time/Date
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
@ -504,6 +509,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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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_
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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("."));
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
@ -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); }
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
31
src/main.cpp
31
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();
|
||||
@ -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)
|
||||
|
||||
|
@ -16,8 +16,8 @@ esp_err_t mqtt_init(void) {
|
||||
// setup network connection
|
||||
WiFi.onEvent(NetworkEvent);
|
||||
ETH.begin();
|
||||
//WiFi.mode(WIFI_STA);
|
||||
//WiFi.begin("SSID", "PASSWORD");
|
||||
// WiFi.mode(WIFI_STA);
|
||||
// WiFi.begin("SSID", "PASSWORD");
|
||||
|
||||
// setup mqtt client
|
||||
mqttClient.begin(MQTT_SERVER, MQTT_PORT, netClient);
|
||||
@ -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
|
18
src/ota.cpp
18
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
|
@ -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
|
||||
|
@ -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
135
src/platformio_orig.ini
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user