commit
76c7d9c4f5
21
README.md
21
README.md
@ -138,9 +138,28 @@ Paxcounter generates identifiers for sniffed MAC adresses and collects them temp
|
|||||||
- Red long blink: LoRaWAN stack error
|
- Red long blink: LoRaWAN stack error
|
||||||
- White long blink: Known Beacon detected
|
- White long blink: Known Beacon detected
|
||||||
|
|
||||||
|
# Sensors and Peripherals
|
||||||
|
|
||||||
|
You can add up to 3 user defined sensors. Insert sensor's payload scheme in [*sensors.cpp*](src/sensors.cpp). Bosch BME280 / BME680 environment sensors are supported. Enable *flag lib_deps_sensors* for your board in [*platformio.ini*](src/platformio.ini) and configure BME in board's hal file before build. If you need Bosch's proprietary BSEC libraray (e.g. to get indoor air quality value from BME680) further enable *build_flags_sensors*, which comes on the price of reduced RAM and increased build size. RTC DS3231, generic serial NMEA GPS, I2C LoPy GPS are supported, and to be configured in board's hal file. See [*generic.h*](src/hal/generic.h) for all options.
|
||||||
|
|
||||||
|
Output of user sensor data can be switched by user remote control command 0x13 sent to Port 2.
|
||||||
|
|
||||||
|
Output of sensor and peripheral data is internally switched by a bitmask register. Default mask (0xFF) can be tailored by editing *cfg.payloadmask* initialization value in [*configmanager.cpp*](src/configmanager.cpp) following this scheme:
|
||||||
|
|
||||||
|
| Bit | Sensordata |
|
||||||
|
|-----|---------------|
|
||||||
|
| 0 | GPS |
|
||||||
|
| 1 | Beacon alarm |
|
||||||
|
| 2 | BME280/680 |
|
||||||
|
| 3 | Paxcounter |
|
||||||
|
| 4 | User sensor 1 |
|
||||||
|
| 5 | User sensor 2 |
|
||||||
|
| 6 | User sensor 3 |
|
||||||
|
| 7 | reserved |
|
||||||
|
|
||||||
# Clock controller
|
# Clock controller
|
||||||
|
|
||||||
Paxcounter can be used to sync a clock which has DCF77 or IF482 time telegram input with an external time source. Use case of this function is to have paxcounter hardware integrated in clocks, and use it for both counting of pax and controlling the clock. Supported external time sources are GPS time, LORAWAN network time (v1.1) and on board RTC time. Precision of the synthetic DCF77 signal depends on precision of on board available time base. Supported are both external time base (e.g. timepulse pin of GPS chip or oscillator output of RTC chip) and internal ESP32 hardware timer. Selection of time base and clock frequency is done by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h).
|
Paxcounter can be used to sync a clock which has DCF77 or IF482 time telegram input with. Use case of this function is to have paxcounter hardware integrated in clocks, and use it for both counting of pax and controlling the clock. Supported external time sources are GPS time, LORAWAN network time (v1.1) and on board RTC time. Precision of the synthetic DCF77 signal depends on precision of on board available time base. Supported are both external time base (e.g. timepulse pin of GPS chip or oscillator output of RTC chip) and internal ESP32 hardware timer. Selection of time base and clock frequency is done by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h).
|
||||||
|
|
||||||
# Payload format
|
# Payload format
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@
|
|||||||
#include "timekeeper.h"
|
#include "timekeeper.h"
|
||||||
|
|
||||||
#define IF482_FRAME_SIZE (17)
|
#define IF482_FRAME_SIZE (17)
|
||||||
#define IF482_PULSE_LENGTH (1000)
|
|
||||||
|
|
||||||
extern HardwareSerial IF482;
|
extern HardwareSerial IF482;
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@
|
|||||||
#include "timesync.h"
|
#include "timesync.h"
|
||||||
#include "timekeeper.h"
|
#include "timekeeper.h"
|
||||||
|
|
||||||
#define TIME_SYNC_SAMPLES 3 // number of time requests for averaging
|
#define TIME_SYNC_SAMPLES 2 // number of time requests for averaging
|
||||||
#define TIME_SYNC_CYCLE 20 // seconds between two time requests
|
#define TIME_SYNC_CYCLE 2 // seconds between two time requests
|
||||||
#define TIME_SYNC_TIMEOUT 120 // timeout seconds waiting for timeserver answer
|
#define TIME_SYNC_TIMEOUT 120 // timeout seconds waiting for timeserver answer
|
||||||
#define TIME_SYNC_TRIGGER 100 // time deviation in millisec triggering a sync
|
#define TIME_SYNC_TRIGGER 100 // time deviation in millisec triggering a sync
|
||||||
#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length
|
#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length
|
||||||
@ -15,6 +15,6 @@
|
|||||||
void send_timesync_req(void);
|
void send_timesync_req(void);
|
||||||
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len);
|
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len);
|
||||||
void process_timesync_req(void *taskparameter);
|
void process_timesync_req(void *taskparameter);
|
||||||
void store_time_sync_req(time_t t_millisec);
|
void store_time_sync_req(uint32_t t_millisec);
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -30,10 +30,10 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
|
|||||||
|
|
||||||
[common]
|
[common]
|
||||||
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
||||||
release_version = 1.7.38
|
release_version = 1.7.39
|
||||||
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
|
; 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
|
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
|
||||||
debug_level = 4
|
debug_level = 3
|
||||||
; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA
|
; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA
|
||||||
upload_protocol = esptool
|
upload_protocol = esptool
|
||||||
;upload_protocol = custom
|
;upload_protocol = custom
|
||||||
|
@ -126,22 +126,6 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "f4c5b6de.f95148",
|
|
||||||
"type": "function",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "Time_Sync_Ans",
|
|
||||||
"func": "/* LoRaWAN Timeserver\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n0 sequence number (taken from node's time_sync_req)\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\n*/\n\n let buf = new ArrayBuffer(6);\n let timestamp = (+new Date(msg.payload.metadata.gateways[0].time));\n \n var seconds = Math.floor(timestamp/1000);\n var fractions = (timestamp % 1000) / 4;\n var seqno = msg.payload.payload_raw[0];\n\n new DataView(buf).setUint8(0, seqno);\n new DataView(buf).setUint32(1, seconds);\n new DataView(buf).setUint8(5, fractions);\n\n msg.payload = new Buffer(new Uint8Array(buf));\n \n return msg;",
|
|
||||||
"outputs": 1,
|
|
||||||
"noerr": 0,
|
|
||||||
"x": 400,
|
|
||||||
"y": 280,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"49e3c067.e782e"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "dac8aafa.389298",
|
"id": "dac8aafa.389298",
|
||||||
"type": "json",
|
"type": "json",
|
||||||
@ -154,7 +138,8 @@
|
|||||||
"y": 360,
|
"y": 360,
|
||||||
"wires": [
|
"wires": [
|
||||||
[
|
[
|
||||||
"72d5e7ee.d1eba8"
|
"72d5e7ee.d1eba8",
|
||||||
|
"f8749724.1ff9f8"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -219,10 +204,40 @@
|
|||||||
"y": 200,
|
"y": 200,
|
||||||
"wires": [
|
"wires": [
|
||||||
[
|
[
|
||||||
"f4c5b6de.f95148"
|
"831ab883.d6a238"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "831ab883.d6a238",
|
||||||
|
"type": "function",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "Generate Time Answer",
|
||||||
|
"func": "/* LoRaWAN Timeserver\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n0 sequence number (taken from node's time_sync_req)\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\n*/\n\nvar gateways = msg.payload.metadata.gateways;\nvar gateway_time = gateways.map(gw => new Date(gw.time));\nvar server_time = new Date(msg.payload.metadata.time);\n\ngateway_time.sort();\n\nvar gw_timestamps = gateway_time.filter(function (element) {\n return element > 0;\n});\n\nvar timestamp = gw_timestamps[0];\n\nif (timestamp < server_time) {\n\n var seconds = Math.floor(timestamp/1000);\n var fractions = (timestamp % 1000) / 4;\n var seqno = msg.payload.payload_raw[0];\n\n let buf = new ArrayBuffer(6);\n new DataView(buf).setUint8(0, seqno);\n new DataView(buf).setUint32(1, seconds);\n new DataView(buf).setUint8(5, fractions);\n\n msg.payload = new Buffer(new Uint8Array(buf));\n \n return msg;\n\n}\n\nelse\n\nreturn null;",
|
||||||
|
"outputs": 1,
|
||||||
|
"noerr": 0,
|
||||||
|
"x": 420,
|
||||||
|
"y": 280,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"49e3c067.e782e"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "f8749724.1ff9f8",
|
||||||
|
"type": "debug",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "time_sync_ans",
|
||||||
|
"active": false,
|
||||||
|
"tosidebar": true,
|
||||||
|
"console": false,
|
||||||
|
"tostatus": true,
|
||||||
|
"complete": "payload",
|
||||||
|
"x": 720,
|
||||||
|
"y": 280,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "2a15ab6f.ab2244",
|
"id": "2a15ab6f.ab2244",
|
||||||
"type": "mqtt-broker",
|
"type": "mqtt-broker",
|
||||||
|
@ -65,7 +65,7 @@ void doHousekeeping() {
|
|||||||
|
|
||||||
// display BME280 sensor data
|
// display BME280 sensor data
|
||||||
#ifdef HAS_BME280
|
#ifdef HAS_BME280
|
||||||
ESP_LOGI(TAG, "BME680 Temp: %.2f°C | Humidity: %.2f | Pressure: %.0f",
|
ESP_LOGI(TAG, "BME280 Temp: %.2f°C | Humidity: %.2f | Pressure: %.0f",
|
||||||
bme_status.temperature, bme_status.humidity, bme_status.pressure);
|
bme_status.temperature, bme_status.humidity, bme_status.pressure);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -22,7 +22,9 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
|
|||||||
TickType_t startTime = xTaskGetTickCount();
|
TickType_t startTime = xTaskGetTickCount();
|
||||||
uint8_t sec = second(t);
|
uint8_t sec = second(t);
|
||||||
|
|
||||||
ESP_LOGD (TAG, "DCF second %d", sec);
|
t = myTZ.toLocal(now());
|
||||||
|
ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] DCF second %d", hour(t), minute(t),
|
||||||
|
second(t), millisecond(), sec);
|
||||||
|
|
||||||
// induce 10 pulses
|
// induce 10 pulses
|
||||||
for (uint8_t pulse = 0; pulse <= 9; pulse++) {
|
for (uint8_t pulse = 0; pulse <= 9; pulse++) {
|
||||||
@ -100,8 +102,8 @@ uint8_t *IRAM_ATTR DCF77_Frame(time_t const tt) {
|
|||||||
} // DCF77_Frame()
|
} // DCF77_Frame()
|
||||||
|
|
||||||
// helper function to convert decimal to bcd digit
|
// helper function to convert decimal to bcd digit
|
||||||
uint8_t IRAM_ATTR dec2bcd(uint8_t const dec, uint8_t const startpos, uint8_t const endpos,
|
uint8_t IRAM_ATTR dec2bcd(uint8_t const dec, uint8_t const startpos,
|
||||||
uint8_t *DCFpulse) {
|
uint8_t const endpos, uint8_t *DCFpulse) {
|
||||||
|
|
||||||
uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
|
uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
|
||||||
uint8_t parity = 0;
|
uint8_t parity = 0;
|
||||||
@ -116,6 +118,8 @@ uint8_t IRAM_ATTR dec2bcd(uint8_t const dec, uint8_t const startpos, uint8_t con
|
|||||||
}
|
}
|
||||||
|
|
||||||
// helper function to encode parity
|
// helper function to encode parity
|
||||||
uint8_t IRAM_ATTR setParityBit(uint8_t const p) { return ((p & 1) ? dcf_1 : dcf_0); }
|
uint8_t IRAM_ATTR setParityBit(uint8_t const p) {
|
||||||
|
return ((p & 1) ? dcf_1 : dcf_0);
|
||||||
|
}
|
||||||
|
|
||||||
#endif // HAS_DCF77
|
#endif // HAS_DCF77
|
@ -90,15 +90,15 @@ HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
|
|||||||
void IF482_Pulse(time_t t) {
|
void IF482_Pulse(time_t t) {
|
||||||
|
|
||||||
static const TickType_t txDelay =
|
static const TickType_t txDelay =
|
||||||
pdMS_TO_TICKS(IF482_PULSE_LENGTH - tx_Ticks(IF482_FRAME_SIZE, HAS_IF482));
|
pdMS_TO_TICKS(1000) - tx_Ticks(IF482_FRAME_SIZE, HAS_IF482);
|
||||||
|
|
||||||
vTaskDelay(txDelay); // wait until moment to fire
|
vTaskDelay(txDelay); // wait until moment to fire
|
||||||
IF482.print(IF482_Frame(t + 1)); // note: if482 telegram for *next* second
|
IF482.print(IF482_Frame(t + 1)); // note: if482 telegram for *next* second
|
||||||
}
|
}
|
||||||
|
|
||||||
String IRAM_ATTR IF482_Frame(time_t startTime) {
|
String IRAM_ATTR IF482_Frame(time_t printTime) {
|
||||||
|
|
||||||
time_t t = myTZ.toLocal(startTime);
|
time_t t = myTZ.toLocal(printTime);
|
||||||
char mon, out[IF482_FRAME_SIZE + 1];
|
char mon, out[IF482_FRAME_SIZE + 1];
|
||||||
|
|
||||||
switch (timeStatus()) { // indicates if time has been set and recently synced
|
switch (timeStatus()) { // indicates if time has been set and recently synced
|
||||||
@ -118,7 +118,9 @@ String IRAM_ATTR IF482_Frame(time_t startTime) {
|
|||||||
year(t) - 2000, month(t), day(t), weekday(t), hour(t), minute(t),
|
year(t) - 2000, month(t), day(t), weekday(t), hour(t), minute(t),
|
||||||
second(t));
|
second(t));
|
||||||
|
|
||||||
ESP_LOGD(TAG, "IF482 = %s", out);
|
t = myTZ.toLocal(now());
|
||||||
|
ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] IF482 = %s", hour(t), minute(t),
|
||||||
|
second(t), millisecond(), out);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,10 +232,6 @@ void onEvent(ev_t ev) {
|
|||||||
#if (TIME_SYNC_TIMESERVER)
|
#if (TIME_SYNC_TIMESERVER)
|
||||||
// if last packet sent was a timesync request, store TX timestamp
|
// if last packet sent was a timesync request, store TX timestamp
|
||||||
if (LMIC.pendTxPort == TIMEPORT) {
|
if (LMIC.pendTxPort == TIMEPORT) {
|
||||||
// store_time_sync_req(now(now_micros), now_micros);
|
|
||||||
// adjust sampled OS time back in time to the nearest second boundary
|
|
||||||
//const ostime_t tAdjust = LMIC.netDeviceTimeFrac * ms2osticks(1000) / 256;
|
|
||||||
//store_time_sync_req(osticks2ms(LMIC.txend - tAdjust)); // milliseconds
|
|
||||||
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
|
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -244,7 +240,7 @@ void onEvent(ev_t ev) {
|
|||||||
: PSTR("TX_COMPLETE"));
|
: PSTR("TX_COMPLETE"));
|
||||||
sprintf(display_line6, " "); // clear previous lmic status
|
sprintf(display_line6, " "); // clear previous lmic status
|
||||||
|
|
||||||
if (LMIC.dataLen) { // did we receive data -> display info
|
if (LMIC.dataLen) { // did we receive payload data -> display info
|
||||||
ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d",
|
ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d",
|
||||||
LMIC.dataLen, LMIC.rssi, LMIC.snr / 4);
|
LMIC.dataLen, LMIC.rssi, LMIC.snr / 4);
|
||||||
sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4);
|
sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4);
|
||||||
@ -429,12 +425,16 @@ esp_err_t lora_stack_init() {
|
|||||||
void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
||||||
// enqueue message in LORA send queue
|
// enqueue message in LORA send queue
|
||||||
BaseType_t ret;
|
BaseType_t ret;
|
||||||
|
MessageBuffer_t DummyBuffer;
|
||||||
switch (prio) {
|
switch (prio) {
|
||||||
case prio_high:
|
case prio_high:
|
||||||
|
// clear space in queue if full, then fallthrough to normal
|
||||||
|
if (uxQueueSpacesAvailable == 0)
|
||||||
|
xQueueReceive(LoraSendQueue, &DummyBuffer, (TickType_t)0);
|
||||||
|
case prio_normal:
|
||||||
ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0);
|
ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0);
|
||||||
break;
|
break;
|
||||||
case prio_low:
|
case prio_low:
|
||||||
case prio_normal:
|
|
||||||
default:
|
default:
|
||||||
ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0);
|
ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0);
|
||||||
break;
|
break;
|
||||||
|
@ -36,8 +36,7 @@ looptask 1 1 arduino core -> runs the LMIC LoRa stack
|
|||||||
irqhandler 1 1 executes tasks triggered by timer irq
|
irqhandler 1 1 executes tasks triggered by timer irq
|
||||||
gpsloop 1 2 reads data from GPS via serial or i2c
|
gpsloop 1 2 reads data from GPS via serial or i2c
|
||||||
bmeloop 1 1 reads data from BME sensor via i2c
|
bmeloop 1 1 reads data from BME sensor via i2c
|
||||||
timesync_ans 1 0 temporary task for receiving time sync requests
|
timesync_req 1 4 temporary task for processing time sync requests
|
||||||
timesync_req 1 0 temporary task for sending time sync requests
|
|
||||||
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
|
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
|
||||||
|
|
||||||
Low priority numbers denote low priority tasks.
|
Low priority numbers denote low priority tasks.
|
||||||
@ -165,7 +164,6 @@ void setup() {
|
|||||||
ARDUINO_LMIC_VERSION_GET_MINOR(ARDUINO_LMIC_VERSION),
|
ARDUINO_LMIC_VERSION_GET_MINOR(ARDUINO_LMIC_VERSION),
|
||||||
ARDUINO_LMIC_VERSION_GET_PATCH(ARDUINO_LMIC_VERSION),
|
ARDUINO_LMIC_VERSION_GET_PATCH(ARDUINO_LMIC_VERSION),
|
||||||
ARDUINO_LMIC_VERSION_GET_LOCAL(ARDUINO_LMIC_VERSION));
|
ARDUINO_LMIC_VERSION_GET_LOCAL(ARDUINO_LMIC_VERSION));
|
||||||
ESP_LOGI(TAG, "DEVEUI: ");
|
|
||||||
showLoraKeys();
|
showLoraKeys();
|
||||||
#endif // HAS_LORA
|
#endif // HAS_LORA
|
||||||
|
|
||||||
@ -187,7 +185,7 @@ void setup() {
|
|||||||
// set low power mode to off
|
// set low power mode to off
|
||||||
#ifdef HAS_LOWPOWER_SWITCH
|
#ifdef HAS_LOWPOWER_SWITCH
|
||||||
pinMode(HAS_LOWPOWER_SWITCH, OUTPUT);
|
pinMode(HAS_LOWPOWER_SWITCH, OUTPUT);
|
||||||
digitalWrite(HAS_LOWPOWER_SWITCH, HIGH);
|
digitalWrite(HAS_LOWPOWER_SWITCH, LOW);
|
||||||
strcat_P(features, " LPWR");
|
strcat_P(features, " LPWR");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -45,25 +45,26 @@ uint8_t *sensor_read(uint8_t sensor) {
|
|||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
|
|
||||||
|
// insert user specific sensor data frames here */
|
||||||
buf[0] = length;
|
buf[0] = length;
|
||||||
buf[1] = 0xff;
|
buf[1] = 0x01;
|
||||||
buf[2] = 0xa0;
|
buf[2] = 0x02;
|
||||||
buf[3] = 0x01;
|
buf[3] = 0x03;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 2:
|
case 2:
|
||||||
|
|
||||||
buf[0] = length;
|
buf[0] = length;
|
||||||
buf[1] = 0xff;
|
buf[1] = 0x01;
|
||||||
buf[2] = 0xa0;
|
buf[2] = 0x02;
|
||||||
buf[3] = 0x02;
|
buf[3] = 0x03;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
|
|
||||||
buf[0] = length;
|
buf[0] = length;
|
||||||
buf[1] = 0xff;
|
buf[1] = 0x01;
|
||||||
buf[2] = 0xa0;
|
buf[2] = 0x02;
|
||||||
buf[3] = 0x03;
|
buf[3] = 0x03;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -150,12 +150,16 @@ esp_err_t spi_init() {
|
|||||||
void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
||||||
// enqueue message in SPI send queue
|
// enqueue message in SPI send queue
|
||||||
BaseType_t ret;
|
BaseType_t ret;
|
||||||
|
MessageBuffer_t DummyBuffer;
|
||||||
switch (prio) {
|
switch (prio) {
|
||||||
case prio_high:
|
case prio_high:
|
||||||
|
// clear space in queue if full, then fallthrough to normal
|
||||||
|
if (!uxQueueSpacesAvailable(SPISendQueue))
|
||||||
|
xQueueReceive(SPISendQueue, &DummyBuffer, (TickType_t)0);
|
||||||
|
case prio_normal:
|
||||||
ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0);
|
ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0);
|
||||||
break;
|
break;
|
||||||
case prio_low:
|
case prio_low:
|
||||||
case prio_normal:
|
|
||||||
default:
|
default:
|
||||||
ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0);
|
ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0);
|
||||||
break;
|
break;
|
||||||
|
@ -219,12 +219,12 @@ void clock_loop(void *taskparameter) { // ClockTask
|
|||||||
xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
|
xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
|
||||||
portMAX_DELAY); // wait for timepulse
|
portMAX_DELAY); // wait for timepulse
|
||||||
|
|
||||||
// no confident time -> we suppress clock output
|
|
||||||
if (timeStatus() == timeNotSet)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
t = time_t(printtime); // UTC time seconds
|
t = time_t(printtime); // UTC time seconds
|
||||||
|
|
||||||
|
// no confident time -> suppress clock output
|
||||||
|
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)))
|
||||||
|
continue;
|
||||||
|
|
||||||
#if defined HAS_IF482
|
#if defined HAS_IF482
|
||||||
|
|
||||||
IF482_Pulse(t);
|
IF482_Pulse(t);
|
||||||
|
161
src/timesync.cpp
161
src/timesync.cpp
@ -20,13 +20,14 @@ static const char TAG[] = __FILE__;
|
|||||||
|
|
||||||
TaskHandle_t timeSyncReqTask;
|
TaskHandle_t timeSyncReqTask;
|
||||||
|
|
||||||
static uint8_t time_sync_seqNo = 0;
|
static uint8_t time_sync_seqNo{};
|
||||||
static bool lora_time_sync_pending = false;
|
static bool lora_time_sync_pending{false};
|
||||||
|
|
||||||
typedef std::chrono::system_clock myClock;
|
typedef std::chrono::system_clock myClock;
|
||||||
typedef myClock::time_point myClock_timepoint;
|
typedef myClock::time_point myClock_timepoint;
|
||||||
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
|
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
|
||||||
myClock_msecTick;
|
myClock_msecTick;
|
||||||
|
typedef std::chrono::duration<double> myClock_secTick;
|
||||||
|
|
||||||
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||||
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||||
@ -39,14 +40,13 @@ void send_timesync_req() {
|
|||||||
ESP_LOGI(TAG, "Timeserver sync request already pending");
|
ESP_LOGI(TAG, "Timeserver sync request already pending");
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
ESP_LOGI(TAG, "Timeserver sync request started");
|
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
||||||
|
|
||||||
lora_time_sync_pending = true;
|
lora_time_sync_pending = true;
|
||||||
|
|
||||||
// clear timestamp array
|
// initialize timestamp array
|
||||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
for (uint8_t i{}; i < TIME_SYNC_SAMPLES; i++)
|
||||||
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint(); // set to epoch
|
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint();
|
||||||
}
|
|
||||||
|
|
||||||
// kick off temporary task for timeserver handshake processing
|
// kick off temporary task for timeserver handshake processing
|
||||||
if (!timeSyncReqTask)
|
if (!timeSyncReqTask)
|
||||||
@ -54,54 +54,29 @@ void send_timesync_req() {
|
|||||||
"timesync_req", // name of task
|
"timesync_req", // name of task
|
||||||
2048, // stack size of task
|
2048, // stack size of task
|
||||||
(void *)1, // task parameter
|
(void *)1, // task parameter
|
||||||
0, // priority of the task
|
4, // priority of the task
|
||||||
&timeSyncReqTask, // task handle
|
&timeSyncReqTask, // task handle
|
||||||
1); // CPU core
|
1); // CPU core
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// process timeserver timestamp answer, called from lorawan.cpp
|
|
||||||
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
|
|
||||||
|
|
||||||
// if no timesync handshake is pending or spurious buffer then exit
|
|
||||||
if ((!lora_time_sync_pending) || (buf_len != TIME_SYNC_FRAME_LENGTH))
|
|
||||||
return 0; // failure
|
|
||||||
|
|
||||||
uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES;
|
|
||||||
uint16_t timestamp_msec = 4 * buf[5]; // convert 1/250th sec fractions to ms
|
|
||||||
uint32_t timestamp_sec = 0, tmp_sec = 0;
|
|
||||||
|
|
||||||
for (uint8_t i = 1; i <= 4; i++) {
|
|
||||||
timestamp_sec = (tmp_sec <<= 8) |= buf[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timestamp_sec + timestamp_msec) // field validation: timestamp not 0 ?
|
|
||||||
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
|
|
||||||
else
|
|
||||||
return 0; // failure
|
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Timesync request #%d rcvd at %d", seq_no,
|
|
||||||
myClock::to_time_t(time_sync_rx[k]));
|
|
||||||
|
|
||||||
// inform processing task
|
|
||||||
if (timeSyncReqTask)
|
|
||||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
|
||||||
|
|
||||||
return 1; // success
|
|
||||||
}
|
|
||||||
|
|
||||||
// task for sending time sync requests
|
// task for sending time sync requests
|
||||||
void process_timesync_req(void *taskparameter) {
|
void process_timesync_req(void *taskparameter) {
|
||||||
|
|
||||||
uint8_t k = 0, i = 0;
|
uint8_t k{};
|
||||||
uint32_t seq_no = 0;
|
uint16_t time_to_set_fraction_msec;
|
||||||
// milliseconds time_offset(0);
|
uint32_t seq_no{}, time_to_set_us;
|
||||||
auto time_offset = myClock_msecTick::zero();
|
long long int time_to_set_ms;
|
||||||
int time_offset_msec;
|
|
||||||
time_t time_to_set;
|
time_t time_to_set;
|
||||||
|
auto time_offset{myClock_msecTick::zero()};
|
||||||
|
|
||||||
|
// wait until we are joined
|
||||||
|
while (!LMIC.devaddr) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||||
|
}
|
||||||
|
|
||||||
// enqueue timestamp samples in lora sendqueue
|
// enqueue timestamp samples in lora sendqueue
|
||||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
for (uint8_t i{}; i < TIME_SYNC_SAMPLES; i++) {
|
||||||
|
|
||||||
// wrap around seqNo 0 .. 254
|
// wrap around seqNo 0 .. 254
|
||||||
time_sync_seqNo = (time_sync_seqNo >= 255) ? 0 : time_sync_seqNo + 1;
|
time_sync_seqNo = (time_sync_seqNo >= 255) ? 0 : time_sync_seqNo + 1;
|
||||||
@ -111,12 +86,12 @@ void process_timesync_req(void *taskparameter) {
|
|||||||
payload.addByte(time_sync_seqNo);
|
payload.addByte(time_sync_seqNo);
|
||||||
SendPayload(TIMEPORT, prio_high);
|
SendPayload(TIMEPORT, prio_high);
|
||||||
|
|
||||||
// process answer
|
// process answer, wait for notification from recv_timesync_ans()
|
||||||
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
||||||
(seq_no != time_sync_seqNo)) {
|
(seq_no != time_sync_seqNo)) {
|
||||||
|
|
||||||
ESP_LOGW(TAG, "Timeserver handshake failed");
|
ESP_LOGW(TAG, "[%0.3f] Timeserver handshake failed", millis() / 1000.0);
|
||||||
goto finish;
|
goto finish;
|
||||||
} // no valid sequence received before timeout
|
} // no valid sequence received before timeout
|
||||||
|
|
||||||
@ -130,19 +105,33 @@ void process_timesync_req(void *taskparameter) {
|
|||||||
|
|
||||||
time_offset += t_rx - t_tx; // cumulate timepoint diffs
|
time_offset += t_rx - t_tx; // cumulate timepoint diffs
|
||||||
|
|
||||||
ESP_LOGD(TAG, "time_offset: %lldms", time_offset.count());
|
if (i < TIME_SYNC_SAMPLES - 1) {
|
||||||
|
// wait until next cycle
|
||||||
if (i < TIME_SYNC_SAMPLES - 1) // wait until next cycle
|
|
||||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||||
|
} else {
|
||||||
|
// send flush to open a receive window for last time_sync_ans
|
||||||
|
payload.reset();
|
||||||
|
payload.addByte(0x99);
|
||||||
|
SendPayload(RCMDPORT, prio_high);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // for
|
} // for
|
||||||
|
|
||||||
// calculate time offset from collected diffs and set time if necessary
|
// calculate time offset from collected diffs
|
||||||
ESP_LOGD(TAG, "Avg time diff: %lldms", time_offset.count());
|
|
||||||
time_offset /= TIME_SYNC_SAMPLES;
|
time_offset /= TIME_SYNC_SAMPLES;
|
||||||
// 1sec wait for top of second
|
ESP_LOGD(TAG, "[%0.3f] avg time diff: %0.3f sec", millis() / 1000.0,
|
||||||
time_to_set = now() + time_offset.count() / 1000 + 1;
|
myClock_secTick(time_offset).count());
|
||||||
ESP_LOGD(TAG, "Calculated UTC epoch time: %d", time_to_set);
|
|
||||||
|
// calculate absolute time with millisecond precision
|
||||||
|
time_to_set_ms = (long long)now(time_to_set_us) * 1000LL +
|
||||||
|
time_to_set_us / 1000LL + time_offset.count();
|
||||||
|
// convert to seconds
|
||||||
|
time_to_set = (time_t)(time_to_set_ms / 1000LL);
|
||||||
|
// calculate fraction milliseconds
|
||||||
|
time_to_set_fraction_msec = (uint16_t)(time_to_set_ms % 1000LL);
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "[%0.3f] Calculated UTC epoch time: %d.%03d sec",
|
||||||
|
millis() / 1000.0, time_to_set, time_to_set_fraction_msec);
|
||||||
|
|
||||||
// adjust system time
|
// adjust system time
|
||||||
if (timeIsValid(time_to_set)) {
|
if (timeIsValid(time_to_set)) {
|
||||||
@ -151,19 +140,23 @@ void process_timesync_req(void *taskparameter) {
|
|||||||
TIME_SYNC_TRIGGER) { // milliseconds threshold
|
TIME_SYNC_TRIGGER) { // milliseconds threshold
|
||||||
|
|
||||||
// wait until top of second
|
// wait until top of second
|
||||||
time_offset_msec = abs(time_offset.count()) % 1000;
|
ESP_LOGD(TAG, "[%0.3f] waiting %d ms", millis() / 1000.0,
|
||||||
ESP_LOGD(TAG, "waiting %dms", 1000 - time_offset_msec);
|
1000 - time_to_set_fraction_msec);
|
||||||
vTaskDelay(pdMS_TO_TICKS(1000 - time_offset_msec));
|
vTaskDelay(pdMS_TO_TICKS(1000 - time_to_set_fraction_msec));
|
||||||
|
|
||||||
setTime(time_to_set);
|
|
||||||
// sync timer pps to top of second
|
// sync timer pps to top of second
|
||||||
if (ppsIRQ)
|
if (ppsIRQ) {
|
||||||
timerWrite(ppsIRQ, 10000); // fire interrupt
|
timerRestart(ppsIRQ); // reset pps timer
|
||||||
|
CLOCKIRQ(); // fire clock pps interrupt
|
||||||
|
}
|
||||||
|
|
||||||
|
setTime(time_to_set + 1);
|
||||||
timeSource = _lora;
|
timeSource = _lora;
|
||||||
|
|
||||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60,
|
timesyncer.attach(TIME_SYNC_INTERVAL * 60,
|
||||||
timeSync); // set to regular repeat
|
timeSync); // set to regular repeat
|
||||||
ESP_LOGI(TAG, "Timesync finished, time adjusted by %lld ms", time_offset.count());
|
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time adjusted by %.3f sec",
|
||||||
|
millis() / 1000.0, myClock_secTick(time_offset).count());
|
||||||
} else
|
} else
|
||||||
ESP_LOGI(TAG, "Timesync finished, time not adjusted, is up to date");
|
ESP_LOGI(TAG, "Timesync finished, time not adjusted, is up to date");
|
||||||
} else
|
} else
|
||||||
@ -177,13 +170,51 @@ finish:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// called from lorawan.cpp after time_sync_req was sent
|
// called from lorawan.cpp after time_sync_req was sent
|
||||||
void store_time_sync_req(time_t t_millisec) {
|
void store_time_sync_req(uint32_t t_millisec) {
|
||||||
|
|
||||||
|
uint8_t k{time_sync_seqNo % TIME_SYNC_SAMPLES};
|
||||||
|
|
||||||
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
|
||||||
time_sync_tx[k] += milliseconds(t_millisec);
|
time_sync_tx[k] += milliseconds(t_millisec);
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Timesync request #%d sent at %d", time_sync_seqNo,
|
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
|
||||||
myClock::to_time_t(time_sync_tx[k]));
|
millis() / 1000.0, time_sync_seqNo, t_millisec / 1000,
|
||||||
|
t_millisec % 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// process timeserver timestamp answer, called from lorawan.cpp
|
||||||
|
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
|
||||||
|
|
||||||
|
// if no timesync handshake is pending or spurious buffer then exit
|
||||||
|
if ((!lora_time_sync_pending) || (buf_len != TIME_SYNC_FRAME_LENGTH))
|
||||||
|
return 0; // failure
|
||||||
|
|
||||||
|
uint8_t seq_no{buf[0]}, k{seq_no % TIME_SYNC_SAMPLES};
|
||||||
|
uint16_t timestamp_msec; // convert 1/250th sec fractions to ms
|
||||||
|
uint32_t timestamp_sec;
|
||||||
|
|
||||||
|
// get the timeserver time.
|
||||||
|
// The first 4 bytes contain the UTC seconds since unix epoch.
|
||||||
|
// Octet order is little endian. Casts are necessary, because buf is an array
|
||||||
|
// of single byte values, and they might overflow when shifted
|
||||||
|
timestamp_sec = ((uint32_t)buf[1]) | (((uint32_t)buf[2]) << 8) |
|
||||||
|
(((uint32_t)buf[3]) << 16) | (((uint32_t)buf[4]) << 24);
|
||||||
|
|
||||||
|
// The 5th byte contains the fractional seconds in 2^-8 second steps
|
||||||
|
timestamp_msec = 4 * buf[5];
|
||||||
|
|
||||||
|
if ((timestamp_sec) || (timestamp_msec)) // field validation: time not 0 ?
|
||||||
|
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
|
||||||
|
else
|
||||||
|
return 0; // failure
|
||||||
|
|
||||||
|
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
|
||||||
|
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec);
|
||||||
|
|
||||||
|
// inform processing task
|
||||||
|
if (timeSyncReqTask)
|
||||||
|
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||||
|
|
||||||
|
return 1; // success
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
Loading…
Reference in New Issue
Block a user