Merge pull request #318 from cyberman54/development

v1.7.39
This commit is contained in:
Verkehrsrot 2019-03-17 15:06:38 +01:00 committed by GitHub
commit 76c7d9c4f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 196 additions and 123 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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