From d4ddb4811fb418f2c7f7a21a321762249a06fff0 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sun, 24 Mar 2019 23:23:00 +0100 Subject: [PATCH 1/3] Timeserver: filter gateway with ms timestamp --- .../Nodered-Timeserver.json | 41 ++++--------------- 1 file changed, 8 insertions(+), 33 deletions(-) rename src/{TTN => Timeserver}/Nodered-Timeserver.json (81%) diff --git a/src/TTN/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json similarity index 81% rename from src/TTN/Nodered-Timeserver.json rename to src/Timeserver/Nodered-Timeserver.json index 5f608a24..b3f68ef9 100644 --- a/src/TTN/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -153,7 +153,7 @@ "y": 200, "wires": [ [ - "f868bce2.dde67" + "831ab883.d6a238" ] ] }, @@ -182,37 +182,12 @@ "y": 40, "wires": [] }, - { - "id": "f868bce2.dde67", - "type": "switch", - "z": "449c1517.e25f4c", - "name": "Timechecker", - "property": "payload.metadata.gateways[0].time", - "propertyType": "msg", - "rules": [ - { - "t": "lte", - "v": "payload.metadata.time", - "vt": "msg" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 590, - "y": 200, - "wires": [ - [ - "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\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];", + "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 gateway_list = msg.payload.metadata.gateways;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.payload.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) 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, "noerr": 0, "x": 360, @@ -251,7 +226,7 @@ "tostatus": true, "complete": "payload", "x": 700, - "y": 280, + "y": 240, "wires": [], "icon": "node-red/bridge.png" }, @@ -268,7 +243,7 @@ "format": "{{msg.payload}}", "layout": "col-center", "x": 810, - "y": 336, + "y": 300, "wires": [] }, { @@ -294,7 +269,7 @@ "seg1": "", "seg2": "", "x": 710, - "y": 416, + "y": 380, "wires": [] }, { @@ -310,7 +285,7 @@ "format": "{{msg.payload}}", "layout": "col-center", "x": 700, - "y": 376, + "y": 340, "wires": [] }, { @@ -322,7 +297,7 @@ "outputs": 1, "noerr": 0, "x": 670, - "y": 336, + "y": 300, "wires": [ [ "8712a5ac.ed18e8" @@ -342,7 +317,7 @@ "format": "{{msg.payload}}", "layout": "col-center", "x": 700, - "y": 456, + "y": 420, "wires": [] }, { From 8de76bcaf5a97a59e0d2b564b814947e84b1cd7b Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Mon, 25 Mar 2019 00:02:37 +0100 Subject: [PATCH 2/3] Timeserver: bugfix (no time 0xff was not sent) --- src/Timeserver/Nodered-Timeserver.json | 32 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index b3f68ef9..e7bb487c 100644 --- a/src/Timeserver/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -49,7 +49,7 @@ "to": "", "reg": false, "x": 240, - "y": 500, + "y": 513, "wires": [ [ "84f1cda2.069e7" @@ -82,7 +82,7 @@ "retain": "", "broker": "2a15ab6f.ab2244", "x": 730, - "y": 500, + "y": 513, "wires": [] }, { @@ -135,7 +135,7 @@ "action": "", "pretty": false, "x": 580, - "y": 500, + "y": 513, "wires": [ [ "72d5e7ee.d1eba8" @@ -165,7 +165,7 @@ "action": "", "property": "payload.payload_raw", "x": 420, - "y": 500, + "y": 513, "wires": [ [ "dac8aafa.389298" @@ -187,8 +187,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 deviceMsg = { payload: msg.payload.dev_id };\nvar gateway_list = msg.payload.metadata.gateways;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.payload.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) 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, + "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 seqno = msg.payload.payload_raw[0];\nvar seqnoMsg = { payload: seqno };\nvar gateway_list = msg.payload.metadata.gateways;\n\n// filter all gateway timestamps that have milliseconds part (which we assume have a \".\")\nvar gateways = gateway_list.filter(function (element) {\n return (element.time.includes(\".\"));\n});\n\nvar gateway_time = gateways.map(gw => {\n return {\n time: new Date(gw.time),\n eui: gw.gtw_id,\n }\n });\nvar server_time = new Date(msg.payload.metadata.time);\n\n// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)\nvar gw_timestamps = gateway_time.filter(function (element) {\n return ((element.time > (server_time - confidence) && element.time <= server_time));\n});\n\n// if no timestamp left, we have no valid one and exit\nif (gw_timestamps.length === 0) {\n var notavailMsg = { payload: \"n/a\" };\n var notimeMsg = { payload: 0xff }; \n var buf2 = Buffer.alloc(1);\n msg.payload = new Buffer(buf2.fill(0xff));\n return [notavailMsg, notavailMsg, deviceMsg, seqnoMsg, msg];}\n\n// sort time array in ascending order to find most recent timestamp for time answer\ngw_timestamps.sort(timecompare);\n\nvar timestamp = gw_timestamps[0].time;\nvar eui = gw_timestamps[0].eui;\nvar offset = server_time - timestamp;\n\nvar seconds = Math.floor(timestamp/1000);\nvar fractions = (timestamp % 1000) / 4;\n\nlet buf = new ArrayBuffer(6);\nnew DataView(buf).setUint8(0, seqno);\nnew DataView(buf).setUint32(1, seconds);\nnew DataView(buf).setUint8(5, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqnoMsg, msg];", + "outputs": 5, "noerr": 0, "x": 360, "y": 340, @@ -204,6 +204,9 @@ [ "a5dbb4ef.019168" ], + [ + "1cb58e7f.221362" + ], [ "49e3c067.e782e" ] @@ -212,6 +215,7 @@ "gw_eui", "offset_ms", "device", + "seq_no", "time_sync_ans" ] }, @@ -320,6 +324,22 @@ "y": 420, "wires": [] }, + { + "id": "1cb58e7f.221362", + "type": "ui_text", + "z": "449c1517.e25f4c", + "group": "edb7cc8d.a3817", + "order": 1, + "width": 0, + "height": 0, + "name": "Sequence No", + "label": "Sequence", + "format": "{{msg.payload}}", + "layout": "col-center", + "x": 700, + "y": 460, + "wires": [] + }, { "id": "2a15ab6f.ab2244", "type": "mqtt-broker", From 74e7c48e93ee61e044714d29ef346f94fa5c1f47 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Mon, 25 Mar 2019 00:03:40 +0100 Subject: [PATCH 3/3] readme.md updated --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d61a1764..f19a4a9c 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ Output of sensor and peripheral data is internally switched by a bitmask registe # Time sync -Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the [**Timeserver code**](/src/TTN/Nodered-Timeserver.json) in TTN subdirectory. Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using. +Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the [**Timeserver code**](/src/Timeserver/Nodered-Timeserver.json) in TTN subdirectory. Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using. # Wall clock controller