From cb65d0ed6b046c2fad7721ce3a52ddac83fc8d4b Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 19:22:58 +0100 Subject: [PATCH 1/7] configmanager: bitwise display of payloadmask --- include/configmanager.h | 4 ++++ src/configmanager.cpp | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/include/configmanager.h b/include/configmanager.h index 96e24e21..afd9c111 100644 --- a/include/configmanager.h +++ b/include/configmanager.h @@ -1,6 +1,10 @@ #ifndef _CONFIGMANAGER_H #define _CONFIGMANAGER_H +#include +#include +#include + void eraseConfig(void); void saveConfig(void); void loadConfig(void); diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 583377a8..f2314789 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -1,8 +1,6 @@ /* configmanager persists runtime configuration using NVRAM of ESP32*/ #include "globals.h" -#include -#include // Local logging tag static const char TAG[] = "flash"; @@ -335,7 +333,7 @@ void loadConfig() { cfg.payloadmask = flash8; ESP_LOGI(TAG, "payloadmask = %d", flash8); } else { - ESP_LOGI(TAG, "payloadmask set to default %d", cfg.payloadmask); + ESP_LOGI(TAG, "payloadmask set to default %d", std::bitset<8>(cfg.payloadmask)); saveConfig(); } From 2a55e9a8c6da300dd0eb4a5b1af6a63f01a35b6f Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 19:24:50 +0100 Subject: [PATCH 2/7] timesync fixes (using ostime_t now) --- include/timesync.h | 2 +- src/lorawan.cpp | 3 +++ src/timesync.cpp | 53 +++++++++++++++++++++++----------------------- 3 files changed, 31 insertions(+), 27 deletions(-) diff --git a/include/timesync.h b/include/timesync.h index ddc7fff6..0cfe5571 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -7,7 +7,7 @@ #include "timekeeper.h" #define TIME_SYNC_SAMPLES 2 // number of time requests for averaging -#define TIME_SYNC_CYCLE 2 // seconds between two time requests +#define TIME_SYNC_CYCLE 20 // seconds between two time requests #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_FRAME_LENGTH 0x06 // timeserver answer frame length diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 7b0f51ee..102911c3 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -490,6 +490,9 @@ void user_request_network_time_callback(void *pVoidUserUTCTime, // Update system time with time read from the network if (timeIsValid(*pUserUTCTime)) { setTime(*pUserUTCTime); +#ifdef HAS_RTC + set_rtctime(*pUserUTCTime); // calibrate RTC if we have one +#endif timeSource = _lora; timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat ESP_LOGI(TAG, "Received recent time from LoRa"); diff --git a/src/timesync.cpp b/src/timesync.cpp index 5c3f2753..514a5ea3 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -20,8 +20,8 @@ static const char TAG[] = __FILE__; TaskHandle_t timeSyncReqTask; -static uint8_t time_sync_seqNo{}; -static bool lora_time_sync_pending{false}; +static uint8_t time_sync_seqNo = 0; +static bool lora_time_sync_pending = false; typedef std::chrono::system_clock myClock; typedef myClock::time_point myClock_timepoint; @@ -44,8 +44,8 @@ void send_timesync_req() { lora_time_sync_pending = true; - // initialize timestamp array - for (uint8_t i{}; i < TIME_SYNC_SAMPLES; i++) + // clear timestamp array + for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint(); // kick off temporary task for timeserver handshake processing @@ -63,12 +63,11 @@ void send_timesync_req() { // task for sending time sync requests void process_timesync_req(void *taskparameter) { - uint8_t k{}; + uint32_t seq_no = 0, time_to_set_us, time_to_set_ms; uint16_t time_to_set_fraction_msec; - uint32_t seq_no{}, time_to_set_us; - long long int time_to_set_ms; + uint8_t k = 0, i = 0; time_t time_to_set; - auto time_offset{myClock_msecTick::zero()}; + auto time_offset = myClock_msecTick::zero(); // wait until we are joined while (!LMIC.devaddr) { @@ -76,7 +75,7 @@ void process_timesync_req(void *taskparameter) { } // enqueue timestamp samples in lora sendqueue - for (uint8_t i{}; i < TIME_SYNC_SAMPLES; i++) { + for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { // wrap around seqNo 0 .. 254 time_sync_seqNo = (time_sync_seqNo >= 255) ? 0 : time_sync_seqNo + 1; @@ -122,20 +121,19 @@ void process_timesync_req(void *taskparameter) { ESP_LOGD(TAG, "[%0.3f] avg time diff: %0.3f sec", millis() / 1000.0, myClock_secTick(time_offset).count()); - // 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(); + // calculate absolute time offset with millisecond precision using time base + // of LMIC os, since we use LMIC's ostime_t txEnd as tx timestamp + time_offset += milliseconds(osticks2ms(os_getTime())); // convert to seconds - time_to_set = (time_t)(time_to_set_ms / 1000LL); + time_to_set = static_cast(myClock_secTick(time_offset).count()); // calculate fraction milliseconds - time_to_set_fraction_msec = (uint16_t)(time_to_set_ms % 1000LL); + time_to_set_fraction_msec = static_cast(time_offset.count() % 1000); 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 if (timeIsValid(time_to_set)) { - if (abs(time_offset.count()) >= TIME_SYNC_TRIGGER) { // milliseconds threshold @@ -150,9 +148,12 @@ void process_timesync_req(void *taskparameter) { CLOCKIRQ(); // fire clock pps interrupt } - setTime(time_to_set + 1); - timeSource = _lora; + setTime(++time_to_set); // +1 sec after waiting for top of seceond +#ifdef HAS_RTC + set_rtctime(time_to_set); // calibrate RTC if we have one +#endif + timeSource = _lora; timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // set to regular repeat ESP_LOGI(TAG, "[%0.3f] Timesync finished, time adjusted by %.3f sec", @@ -170,15 +171,15 @@ finish: } // called from lorawan.cpp after time_sync_req was sent -void store_time_sync_req(uint32_t t_millisec) { +void store_time_sync_req(uint32_t t_txEnd_ms) { - 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_txEnd_ms); ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d", - millis() / 1000.0, time_sync_seqNo, t_millisec / 1000, - t_millisec % 1000); + millis() / 1000.0, time_sync_seqNo, t_txEnd_ms / 1000, + t_txEnd_ms % 1000); } // process timeserver timestamp answer, called from lorawan.cpp @@ -188,16 +189,16 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { 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}; + 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 + // 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[1]) | (((uint32_t)buf[2]) << 8) | - (((uint32_t)buf[3]) << 16) | (((uint32_t)buf[4]) << 24); + 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]; From 9e4da009cae4f7a67cf0a380b6a3a762a1cfd620 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 21:09:01 +0100 Subject: [PATCH 3/7] Nodered Timeserver eui display --- src/TTN/Nodered-Timeserver.json | 44 +++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/src/TTN/Nodered-Timeserver.json b/src/TTN/Nodered-Timeserver.json index 4f6d30a3..6dd9ac2a 100644 --- a/src/TTN/Nodered-Timeserver.json +++ b/src/TTN/Nodered-Timeserver.json @@ -48,8 +48,8 @@ "from": "", "to": "", "reg": false, - "x": 220, - "y": 360, + "x": 200, + "y": 400, "wires": [ [ "84f1cda2.069e7" @@ -82,7 +82,7 @@ "retain": "", "broker": "2a15ab6f.ab2244", "x": 690, - "y": 360, + "y": 400, "wires": [] }, { @@ -135,11 +135,10 @@ "action": "", "pretty": false, "x": 540, - "y": 360, + "y": 400, "wires": [ [ - "72d5e7ee.d1eba8", - "f8749724.1ff9f8" + "72d5e7ee.d1eba8" ] ] }, @@ -166,7 +165,7 @@ "action": "", "property": "payload.payload_raw", "x": 380, - "y": 360, + "y": 400, "wires": [ [ "dac8aafa.389298" @@ -213,30 +212,39 @@ "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, + "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 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;\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 };\n\nreturn [infoMsg, msg];", + "outputs": 2, "noerr": 0, - "x": 420, - "y": 280, + "x": 380, + "y": 300, "wires": [ + [ + "37722d4b.08e3c2", + "8712a5ac.ed18e8" + ], [ "49e3c067.e782e" ] + ], + "outputLabels": [ + "gw_eui", + "time_sync_ans" ] }, { - "id": "f8749724.1ff9f8", + "id": "37722d4b.08e3c2", "type": "debug", "z": "449c1517.e25f4c", - "name": "time_sync_ans", - "active": false, - "tosidebar": true, + "name": "Timeserver Gw", + "active": true, + "tosidebar": false, "console": false, "tostatus": true, "complete": "payload", - "x": 720, - "y": 280, - "wires": [] + "x": 660, + "y": 260, + "wires": [], + "icon": "node-red/bridge.png" }, { "id": "2a15ab6f.ab2244", From 2d93f323b4827cd3aae1db3f474ee6c53d343982 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 22:00:02 +0100 Subject: [PATCH 4/7] configmanager bitmask display --- include/configmanager.h | 1 - src/configmanager.cpp | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/configmanager.h b/include/configmanager.h index afd9c111..373cc1af 100644 --- a/include/configmanager.h +++ b/include/configmanager.h @@ -3,7 +3,6 @@ #include #include -#include void eraseConfig(void); void saveConfig(void); diff --git a/src/configmanager.cpp b/src/configmanager.cpp index f2314789..936590e5 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -82,7 +82,8 @@ void saveConfig() { 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) + 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); @@ -213,8 +214,7 @@ void loadConfig() { 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]); + ESP_LOGI(TAG, "bsecstate = %d", cfg.bsecstate[BSEC_MAX_STATE_BLOB_SIZE]); }; if (nvs_get_i8(my_handle, "lorasf", &flash8) == ESP_OK) { @@ -331,9 +331,9 @@ void loadConfig() { if (nvs_get_i8(my_handle, "payloadmask", &flash8) == ESP_OK) { cfg.payloadmask = flash8; - ESP_LOGI(TAG, "payloadmask = %d", flash8); + ESP_LOGI(TAG, "payloadmask = %hhu", flash8); } else { - ESP_LOGI(TAG, "payloadmask set to default %d", std::bitset<8>(cfg.payloadmask)); + ESP_LOGI(TAG, "payloadmask set to default %hhu", cfg.payloadmask); saveConfig(); } From cd67cb233511e7c1a65313d11e4a50edf289be3a Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 22:00:14 +0100 Subject: [PATCH 5/7] Nodred Timeserver graph display --- src/TTN/Nodered-Timeserver.json | 81 ++++++++++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/src/TTN/Nodered-Timeserver.json b/src/TTN/Nodered-Timeserver.json index 6dd9ac2a..24f0133a 100644 --- a/src/TTN/Nodered-Timeserver.json +++ b/src/TTN/Nodered-Timeserver.json @@ -49,7 +49,7 @@ "to": "", "reg": false, "x": 200, - "y": 400, + "y": 420, "wires": [ [ "84f1cda2.069e7" @@ -82,7 +82,7 @@ "retain": "", "broker": "2a15ab6f.ab2244", "x": 690, - "y": 400, + "y": 420, "wires": [] }, { @@ -135,7 +135,7 @@ "action": "", "pretty": false, "x": 540, - "y": 400, + "y": 420, "wires": [ [ "72d5e7ee.d1eba8" @@ -165,7 +165,7 @@ "action": "", "property": "payload.payload_raw", "x": 380, - "y": 400, + "y": 420, "wires": [ [ "dac8aafa.389298" @@ -212,8 +212,8 @@ "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\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 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;\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 };\n\nreturn [infoMsg, msg];", - "outputs": 2, + "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 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 };\n\nreturn [infoMsg, msg, offsetMsg];", + "outputs": 3, "noerr": 0, "x": 380, "y": 300, @@ -224,11 +224,15 @@ ], [ "49e3c067.e782e" + ], + [ + "46ce842a.614d5c" ] ], "outputLabels": [ "gw_eui", - "time_sync_ans" + "time_sync_ans", + "offset_ms" ] }, { @@ -246,6 +250,48 @@ "wires": [], "icon": "node-red/bridge.png" }, + { + "id": "8712a5ac.ed18e8", + "type": "ui_text", + "z": "449c1517.e25f4c", + "group": "edb7cc8d.a3817", + "order": 0, + "width": 0, + "height": 0, + "name": "Timeserver", + "label": "Recent timeserver was:", + "format": "{{msg.payload}}", + "layout": "col-center", + "x": 670, + "y": 320, + "wires": [] + }, + { + "id": "46ce842a.614d5c", + "type": "ui_gauge", + "z": "449c1517.e25f4c", + "name": "offset", + "group": "edb7cc8d.a3817", + "order": 1, + "width": 0, + "height": 0, + "gtype": "gage", + "title": "Offset [ms]", + "label": "units", + "format": "{{value}}", + "min": 0, + "max": "2000", + "colors": [ + "#00b500", + "#e6e600", + "#ca3838" + ], + "seg1": "", + "seg2": "", + "x": 690, + "y": 360, + "wires": [] + }, { "id": "2a15ab6f.ab2244", "type": "mqtt-broker", @@ -268,5 +314,26 @@ "willTopic": "", "willQos": "0", "willPayload": "" + }, + { + "id": "edb7cc8d.a3817", + "type": "ui_group", + "z": "", + "name": "Timeserver", + "tab": "d525a5d.0832858", + "order": 4, + "disp": true, + "width": "6", + "collapse": false + }, + { + "id": "d525a5d.0832858", + "type": "ui_tab", + "z": "", + "name": "Timeserver", + "icon": "sync", + "order": 3, + "disabled": false, + "hidden": false } ] \ No newline at end of file From 5cb875bcfcf2a7addf732c724ebabafd3dc41b6b Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 22:00:42 +0100 Subject: [PATCH 6/7] timesync TIME_SYNC_FIXUP --- include/timesync.h | 1 + src/timesync.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/include/timesync.h b/include/timesync.h index 0cfe5571..f6d8fe80 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -11,6 +11,7 @@ #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_FRAME_LENGTH 0x06 // timeserver answer frame length +#define TIME_SYNC_FIXUP 0 // calibration millisec to fixup processing time void send_timesync_req(void); int recv_timesync_ans(uint8_t buf[], uint8_t buf_len); diff --git a/src/timesync.cpp b/src/timesync.cpp index 514a5ea3..3e62a9fb 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -124,6 +124,8 @@ void process_timesync_req(void *taskparameter) { // calculate absolute time offset with millisecond precision using time base // of LMIC os, since we use LMIC's ostime_t txEnd as tx timestamp time_offset += milliseconds(osticks2ms(os_getTime())); + // apply calibration factor for processing time + time_offset += milliseconds(TIME_SYNC_FIXUP); // convert to seconds time_to_set = static_cast(myClock_secTick(time_offset).count()); // calculate fraction milliseconds @@ -138,9 +140,9 @@ void process_timesync_req(void *taskparameter) { TIME_SYNC_TRIGGER) { // milliseconds threshold // wait until top of second - ESP_LOGD(TAG, "[%0.3f] waiting %d ms", millis() / 1000.0, - 1000 - time_to_set_fraction_msec); - vTaskDelay(pdMS_TO_TICKS(1000 - time_to_set_fraction_msec)); + uint16_t const wait_ms = 1000 - time_to_set_fraction_msec; + ESP_LOGD(TAG, "[%0.3f] waiting %d ms", millis() / 1000.0, wait_ms); + vTaskDelay(pdMS_TO_TICKS(wait_ms)); // sync timer pps to top of second if (ppsIRQ) { From 6a920ce12a71d92a98d44e4cb3e49e46db386e23 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 17 Mar 2019 22:04:28 +0100 Subject: [PATCH 7/7] Nodered Timeserver fixes --- src/TTN/Nodered-Timeserver.json | 96 ++++++++++++++++----------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/src/TTN/Nodered-Timeserver.json b/src/TTN/Nodered-Timeserver.json index 24f0133a..37fe6063 100644 --- a/src/TTN/Nodered-Timeserver.json +++ b/src/TTN/Nodered-Timeserver.json @@ -1,4 +1,30 @@ [ + { + "id": "46ce842a.614d5c", + "type": "ui_gauge", + "z": "449c1517.e25f4c", + "name": "Timeserver offset", + "group": "edb7cc8d.a3817", + "order": 1, + "width": 0, + "height": 0, + "gtype": "gage", + "title": "Offset gateway to server", + "label": "milliseconds", + "format": "{{value}}", + "min": 0, + "max": "2000", + "colors": [ + "#00b500", + "#e6e600", + "#ca3838" + ], + "seg1": "", + "seg2": "", + "x": 690, + "y": 360, + "wires": [] + }, { "id": "49e3c067.e782e", "type": "change", @@ -48,7 +74,7 @@ "from": "", "to": "", "reg": false, - "x": 200, + "x": 240, "y": 420, "wires": [ [ @@ -64,7 +90,7 @@ "topic": "+/devices/+/up", "qos": "2", "broker": "2a15ab6f.ab2244", - "x": 70, + "x": 110, "y": 120, "wires": [ [ @@ -81,7 +107,7 @@ "qos": "", "retain": "", "broker": "2a15ab6f.ab2244", - "x": 690, + "x": 730, "y": 420, "wires": [] }, @@ -93,7 +119,7 @@ "property": "payload", "action": "", "pretty": false, - "x": 220, + "x": 260, "y": 200, "wires": [ [ @@ -118,7 +144,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 220, + "x": 260, "y": 120, "wires": [ [ @@ -134,7 +160,7 @@ "property": "payload", "action": "", "pretty": false, - "x": 540, + "x": 580, "y": 420, "wires": [ [ @@ -149,7 +175,7 @@ "name": "Decode", "action": "", "property": "payload.payload_raw", - "x": 380, + "x": 420, "y": 200, "wires": [ [ @@ -164,7 +190,7 @@ "name": "Encode", "action": "", "property": "payload.payload_raw", - "x": 380, + "x": 420, "y": 420, "wires": [ [ @@ -178,7 +204,7 @@ "z": "449c1517.e25f4c", "name": "LoRaWAN Timeserver", "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": 120, + "x": 160, "y": 40, "wires": [] }, @@ -199,7 +225,7 @@ "checkall": "true", "repair": false, "outputs": 1, - "x": 550, + "x": 590, "y": 200, "wires": [ [ @@ -215,7 +241,7 @@ "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 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 };\n\nreturn [infoMsg, msg, offsetMsg];", "outputs": 3, "noerr": 0, - "x": 380, + "x": 420, "y": 300, "wires": [ [ @@ -245,7 +271,7 @@ "console": false, "tostatus": true, "complete": "payload", - "x": 660, + "x": 700, "y": 260, "wires": [], "icon": "node-red/bridge.png" @@ -262,35 +288,20 @@ "label": "Recent timeserver was:", "format": "{{msg.payload}}", "layout": "col-center", - "x": 670, + "x": 710, "y": 320, "wires": [] }, { - "id": "46ce842a.614d5c", - "type": "ui_gauge", - "z": "449c1517.e25f4c", - "name": "offset", - "group": "edb7cc8d.a3817", - "order": 1, - "width": 0, - "height": 0, - "gtype": "gage", - "title": "Offset [ms]", - "label": "units", - "format": "{{value}}", - "min": 0, - "max": "2000", - "colors": [ - "#00b500", - "#e6e600", - "#ca3838" - ], - "seg1": "", - "seg2": "", - "x": 690, - "y": 360, - "wires": [] + "id": "edb7cc8d.a3817", + "type": "ui_group", + "z": "", + "name": "Timeserver", + "tab": "d525a5d.0832858", + "order": 4, + "disp": true, + "width": "6", + "collapse": false }, { "id": "2a15ab6f.ab2244", @@ -315,17 +326,6 @@ "willQos": "0", "willPayload": "" }, - { - "id": "edb7cc8d.a3817", - "type": "ui_group", - "z": "", - "name": "Timeserver", - "tab": "d525a5d.0832858", - "order": 4, - "disp": true, - "width": "6", - "collapse": false - }, { "id": "d525a5d.0832858", "type": "ui_tab",