timesync errorhandling improved

This commit is contained in:
Verkehrsrot 2019-03-19 00:02:35 +01:00
parent 9986902833
commit 6efe299e71
3 changed files with 62 additions and 42 deletions

View File

@ -1,11 +1,4 @@
[ [
{
"id": "449c1517.e25f4c",
"type": "tab",
"label": "Timeserver MQTT",
"disabled": false,
"info": ""
},
{ {
"id": "49e3c067.e782e", "id": "49e3c067.e782e",
"type": "change", "type": "change",
@ -219,7 +212,7 @@
"type": "function", "type": "function",
"z": "449c1517.e25f4c", "z": "449c1517.e25f4c",
"name": "Generate Time Answer", "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\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 = 2000; // max millisecond diff gateway time to server time\n\nvar device = msg.payload.dev_id;\nvar gateways = msg.payload.metadata.gateways;\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) return null;\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;\nvar seqno = msg.payload.payload_raw[0];\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));\nvar infoMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\nvar deviceMsg = { payload: device };\n\nreturn [infoMsg, offsetMsg, deviceMsg, msg];", "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\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 = 2000; // max millisecond diff gateway time to server time\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar gateways = msg.payload.metadata.gateways;\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) return [\"n/a\", \"n/a\", deviceMsg, 0xff];\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;\nvar seqno = msg.payload.payload_raw[0];\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));\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, msg];",
"outputs": 4, "outputs": 4,
"noerr": 0, "noerr": 0,
"x": 360, "x": 360,

View File

@ -46,7 +46,7 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore
// ESP32 hardware timers // ESP32 hardware timers
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS in paxcounter.conf) 0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
1 ppsIRQ -> pps clock irq -> 1sec 1 ppsIRQ -> pps clock irq -> 1sec
2 unused 2 unused
3 unused 3 unused
@ -156,7 +156,7 @@ void setup() {
ESP.getFlashChipSpeed()); ESP.getFlashChipSpeed());
ESP_LOGI(TAG, "Wifi/BT software coexist version %s", esp_coex_version_get()); ESP_LOGI(TAG, "Wifi/BT software coexist version %s", esp_coex_version_get());
#if(HAS_LORA) #if (HAS_LORA)
ESP_LOGI(TAG, "IBM LMIC version %d.%d.%d", LMIC_VERSION_MAJOR, ESP_LOGI(TAG, "IBM LMIC version %d.%d.%d", LMIC_VERSION_MAJOR,
LMIC_VERSION_MINOR, LMIC_VERSION_BUILD); LMIC_VERSION_MINOR, LMIC_VERSION_BUILD);
ESP_LOGI(TAG, "Arduino LMIC version %d.%d.%d.%d", ESP_LOGI(TAG, "Arduino LMIC version %d.%d.%d.%d",
@ -167,7 +167,7 @@ void setup() {
showLoraKeys(); showLoraKeys();
#endif // HAS_LORA #endif // HAS_LORA
#if(HAS_GPS) #if (HAS_GPS)
ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion()); ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion());
#endif #endif
@ -185,7 +185,12 @@ 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);
#if (LOW_POWER_ACTIVE_LOW)
digitalWrite(HAS_LOWPOWER_SWITCH, HIGH);
#else
digitalWrite(HAS_LOWPOWER_SWITCH, LOW); digitalWrite(HAS_LOWPOWER_SWITCH, LOW);
#endif
strcat_P(features, " LPWR"); strcat_P(features, " LPWR");
#endif #endif
@ -269,7 +274,7 @@ void setup() {
#endif // HAS_BUTTON #endif // HAS_BUTTON
// initialize gps // initialize gps
#if(HAS_GPS) #if (HAS_GPS)
strcat_P(features, " GPS"); strcat_P(features, " GPS");
if (gps_init()) { if (gps_init()) {
ESP_LOGI(TAG, "Starting GPS Feed..."); ESP_LOGI(TAG, "Starting GPS Feed...");
@ -284,13 +289,13 @@ void setup() {
#endif #endif
// initialize sensors // initialize sensors
#if(HAS_SENSORS) #if (HAS_SENSORS)
strcat_P(features, " SENS"); strcat_P(features, " SENS");
sensor_init(); sensor_init();
#endif #endif
// initialize LoRa // initialize LoRa
#if(HAS_LORA) #if (HAS_LORA)
strcat_P(features, " LORA"); strcat_P(features, " LORA");
assert(lora_stack_init() == ESP_OK); assert(lora_stack_init() == ESP_OK);
#endif #endif
@ -425,7 +430,7 @@ void setup() {
void loop() { void loop() {
while (1) { while (1) {
#if(HAS_LORA) #if (HAS_LORA)
os_runloop_once(); // execute lmic scheduled jobs and events os_runloop_once(); // execute lmic scheduled jobs and events
#endif #endif
delay(2); // yield to CPU delay(2); // yield to CPU

View File

@ -37,7 +37,7 @@ void send_timesync_req() {
// if a timesync handshake is pending then exit // if a timesync handshake is pending then exit
if (lora_time_sync_pending) { if (lora_time_sync_pending) {
ESP_LOGI(TAG, "Timeserver sync request already pending"); // ESP_LOGI(TAG, "Timeserver sync request already pending");
return; return;
} else { } else {
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0); ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
@ -90,7 +90,8 @@ void process_timesync_req(void *taskparameter) {
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, "[%0.3f] Timeserver handshake failed", millis() / 1000.0); ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
millis() / 1000.0);
goto finish; goto finish;
} // no valid sequence received before timeout } // no valid sequence received before timeout
@ -161,9 +162,12 @@ void process_timesync_req(void *taskparameter) {
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time adjusted by %.3f sec", ESP_LOGI(TAG, "[%0.3f] Timesync finished, time adjusted by %.3f sec",
millis() / 1000.0, myClock_secTick(time_offset).count()); 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,
"[%0.3f] Timesync finished, time is up to date",
millis() / 1000.0);
} else } else
ESP_LOGW(TAG, "Invalid time received from timeserver"); ESP_LOGW(TAG, "[%0.3f] Timesync failed, outdated time calculated",
millis() / 1000.0);
finish: finish:
@ -188,36 +192,54 @@ void store_time_sync_req(uint32_t t_txEnd_ms) {
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
// if no timesync handshake is pending or spurious buffer then exit // if no timesync handshake is pending or spurious buffer then exit
if ((!lora_time_sync_pending) || (buf_len != TIME_SYNC_FRAME_LENGTH)) if (!lora_time_sync_pending)
return 0; // failure return 0; // failure
uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES; // if no time is available or spurious buffer then exit
uint16_t timestamp_msec; // convert 1/250th sec fractions to ms if (buf_len != TIME_SYNC_FRAME_LENGTH) {
uint32_t timestamp_sec; if (buf[0] == 0xff)
ESP_LOGI(TAG, "[%0.3f] Timeserver error: no confident time available",
millis() / 1000.0);
else
ESP_LOGW(TAG, "[%0.3f] Timeserver error: spurious data received",
millis() / 1000.0);
return 0; // failure
}
// get the timeserver time. else { // we received a probably valid time frame
// The first 4 bytes contain the UTC seconds since unix epoch.
// Octet order is big endian. Casts are necessary, because buf is an array
// of single byte values, and they might overflow when shifted
timestamp_sec = ((uint32_t)buf[4]) | (((uint32_t)buf[3]) << 8) |
(((uint32_t)buf[2]) << 16) | (((uint32_t)buf[1]) << 24);
// The 5th byte contains the fractional seconds in 2^-8 second steps uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES;
timestamp_msec = 4 * buf[5]; uint16_t timestamp_msec; // convert 1/250th sec fractions to ms
uint32_t timestamp_sec;
if ((timestamp_sec) || (timestamp_msec)) // field validation: time not 0 ? // fetch timeserver time from 4 bytes containing the UTC seconds since unix
// epoch. Octet order is big endian. Casts are necessary, because buf is an
// array of single byte values, and they might overflow when shifted
timestamp_sec = ((uint32_t)buf[4]) | (((uint32_t)buf[3]) << 8) |
(((uint32_t)buf[2]) << 16) | (((uint32_t)buf[1]) << 24);
// the 5th byte contains the fractional seconds in 2^-8 second steps
timestamp_msec = 4 * buf[5];
// construct the timepoint when message was seen on gateway
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec); 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", // guess if the timepoint is recent by comparing with code compile date
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec); if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec);
// inform processing task // inform processing task
if (timeSyncReqTask) if (timeSyncReqTask)
xTaskNotify(timeSyncReqTask, seq_no, eSetBits); xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
return 1; // success return 1; // success
} else {
ESP_LOGW(TAG, "[%0.3f] Timeserver error: outdated time received",
millis() / 1000.0);
return 0; // failure
}
}
} }
#endif #endif