From 83252fc62c54ec41d0a95d7afa636ab86531b124 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 12:48:17 +0200 Subject: [PATCH 1/9] SyncToPPS interrupt IRAM_ATTR set --- lib/microTime/src/TimeLib.h | 2 +- lib/microTime/src/microTime.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/microTime/src/TimeLib.h b/lib/microTime/src/TimeLib.h index 45efb398..2e3a7f44 100644 --- a/lib/microTime/src/TimeLib.h +++ b/lib/microTime/src/TimeLib.h @@ -154,7 +154,7 @@ time_t now(uint32_t &sysTimeMicros); // return the current time as seconds and #endif #ifdef usePPS -void SyncToPPS(); +void IRAM_ATTR SyncToPPS(); #endif void setTime(time_t t); void setTime(int hr, int min, int sec, int day, int month, int yr); diff --git a/lib/microTime/src/microTime.cpp b/lib/microTime/src/microTime.cpp index 4dd745ad..055c8cf7 100644 --- a/lib/microTime/src/microTime.cpp +++ b/lib/microTime/src/microTime.cpp @@ -260,7 +260,7 @@ time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync #endif #ifdef usePPS -void SyncToPPS() { +void IRAM_ATTR SyncToPPS() { sysTime++; prevMicros = micros(); } From 11f184e67a11fd4ef0834106279bd1f564b6a6c7 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 12:55:18 +0200 Subject: [PATCH 2/9] use TaskNotifyFrom ISR inside Ticker functions --- src/cyclic.cpp | 2 +- src/senddata.cpp | 2 +- src/timekeeper.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cyclic.cpp b/src/cyclic.cpp index eb3f9a99..51a72671 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -9,7 +9,7 @@ static const char TAG[] = __FILE__; Ticker housekeeper; -void housekeeping() { xTaskNotify(irqHandlerTask, CYCLIC_IRQ, eSetBits); } +void housekeeping() { xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL); } // do all housekeeping void doHousekeeping() { diff --git a/src/senddata.cpp b/src/senddata.cpp index 9eb475b9..be84c677 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -3,7 +3,7 @@ Ticker sendcycler; -void sendcycle() { xTaskNotify(irqHandlerTask, SENDCYCLE_IRQ, eSetBits); } +void sendcycle() { xTaskNotifyFromISR(irqHandlerTask, SENDCYCLE_IRQ, eSetBits, NULL); } // put data to send in RTos Queues used for transmit over channels Lora and SPI void SendPayload(uint8_t port, sendprio_t prio) { diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index addd7f85..8077693b 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -20,7 +20,7 @@ HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS) Ticker timesyncer; -void timeSync() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); } +void timeSync() { xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL); } time_t timeProvider(void) { From dd661254b5bc211653bcddef99d9c3601e344ea7 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 16:43:12 +0200 Subject: [PATCH 3/9] New 5-byte time sync protocol --- include/timesync.h | 4 +- src/Timeserver/Nodered-Timeserver.json | 388 +------------------------ src/lorawan.cpp | 18 +- src/paxcounter.conf | 4 +- src/timesync.cpp | 34 ++- 5 files changed, 38 insertions(+), 410 deletions(-) diff --git a/include/timesync.h b/include/timesync.h index 0e96f084..31dcf6b6 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -7,11 +7,11 @@ #include "timekeeper.h" //#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds] -#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length [bytes] +#define TIME_SYNC_FRAME_LENGTH 0x05 // timeserver answer frame length [bytes] #define TIME_SYNC_FIXUP 6 // calibration to fixup processing time [milliseconds] void send_timesync_req(void); -int recv_timesync_ans(uint8_t buf[], uint8_t buf_len); +int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len); void process_timesync_req(void *taskparameter); void store_time_sync_req(uint32_t t_millisec); void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec); diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index e7bb487c..3d08a95b 100644 --- a/src/Timeserver/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -1,387 +1 @@ -[ - { - "id": "49e3c067.e782e", - "type": "change", - "z": "449c1517.e25f4c", - "name": "Payload", - "rules": [ - { - "t": "change", - "p": "topic", - "pt": "msg", - "from": "up", - "fromt": "str", - "to": "down", - "tot": "str" - }, - { - "t": "move", - "p": "payload", - "pt": "msg", - "to": "payload.payload_raw", - "tot": "msg" - }, - { - "t": "set", - "p": "payload.port", - "pt": "msg", - "to": "9", - "tot": "num" - }, - { - "t": "set", - "p": "payload.confirmed", - "pt": "msg", - "to": "false", - "tot": "bool" - }, - { - "t": "set", - "p": "payload.schedule", - "pt": "msg", - "to": "replace", - "tot": "str" - } - ], - "action": "", - "property": "", - "from": "", - "to": "", - "reg": false, - "x": 240, - "y": 513, - "wires": [ - [ - "84f1cda2.069e7" - ] - ] - }, - { - "id": "cc140589.dea168", - "type": "mqtt in", - "z": "449c1517.e25f4c", - "name": "listen", - "topic": "+/devices/+/up", - "qos": "2", - "broker": "2a15ab6f.ab2244", - "x": 110, - "y": 120, - "wires": [ - [ - "9f4b8dd3.2f0d2" - ] - ] - }, - { - "id": "72d5e7ee.d1eba8", - "type": "mqtt out", - "z": "449c1517.e25f4c", - "name": "send", - "topic": "", - "qos": "", - "retain": "", - "broker": "2a15ab6f.ab2244", - "x": 730, - "y": 513, - "wires": [] - }, - { - "id": "4f97d75.6c87528", - "type": "json", - "z": "449c1517.e25f4c", - "name": "Convert", - "property": "payload", - "action": "", - "pretty": false, - "x": 260, - "y": 200, - "wires": [ - [ - "8ed813a9.a9319" - ] - ] - }, - { - "id": "9f4b8dd3.2f0d2", - "type": "switch", - "z": "449c1517.e25f4c", - "name": "Timeport", - "property": "payload", - "propertyType": "msg", - "rules": [ - { - "t": "cont", - "v": "\"port\":9", - "vt": "str" - } - ], - "checkall": "true", - "repair": false, - "outputs": 1, - "x": 260, - "y": 120, - "wires": [ - [ - "4f97d75.6c87528" - ] - ] - }, - { - "id": "dac8aafa.389298", - "type": "json", - "z": "449c1517.e25f4c", - "name": "Convert", - "property": "payload", - "action": "", - "pretty": false, - "x": 580, - "y": 513, - "wires": [ - [ - "72d5e7ee.d1eba8" - ] - ] - }, - { - "id": "8ed813a9.a9319", - "type": "base64", - "z": "449c1517.e25f4c", - "name": "Decode", - "action": "", - "property": "payload.payload_raw", - "x": 420, - "y": 200, - "wires": [ - [ - "831ab883.d6a238" - ] - ] - }, - { - "id": "84f1cda2.069e7", - "type": "base64", - "z": "449c1517.e25f4c", - "name": "Encode", - "action": "", - "property": "payload.payload_raw", - "x": 420, - "y": 513, - "wires": [ - [ - "dac8aafa.389298" - ] - ] - }, - { - "id": "6190967b.01f758", - "type": "comment", - "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": 160, - "y": 40, - "wires": [] - }, - { - "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 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, - "wires": [ - [ - "37722d4b.08e3c2", - "a8a04c7a.c5fbd", - "a15454a9.fa0948" - ], - [ - "46ce842a.614d5c" - ], - [ - "a5dbb4ef.019168" - ], - [ - "1cb58e7f.221362" - ], - [ - "49e3c067.e782e" - ] - ], - "outputLabels": [ - "gw_eui", - "offset_ms", - "device", - "seq_no", - "time_sync_ans" - ] - }, - { - "id": "37722d4b.08e3c2", - "type": "debug", - "z": "449c1517.e25f4c", - "name": "Timeserver Gw", - "active": false, - "tosidebar": false, - "console": false, - "tostatus": true, - "complete": "payload", - "x": 700, - "y": 240, - "wires": [], - "icon": "node-red/bridge.png" - }, - { - "id": "8712a5ac.ed18e8", - "type": "ui_text", - "z": "449c1517.e25f4c", - "group": "edb7cc8d.a3817", - "order": 3, - "width": 0, - "height": 0, - "name": "Recent time", - "label": "Last answer at:", - "format": "{{msg.payload}}", - "layout": "col-center", - "x": 810, - "y": 300, - "wires": [] - }, - { - "id": "46ce842a.614d5c", - "type": "ui_gauge", - "z": "449c1517.e25f4c", - "name": "Timeserver offset", - "group": "edb7cc8d.a3817", - "order": 2, - "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": 710, - "y": 380, - "wires": [] - }, - { - "id": "a8a04c7a.c5fbd", - "type": "ui_text", - "z": "449c1517.e25f4c", - "group": "edb7cc8d.a3817", - "order": 1, - "width": 0, - "height": 0, - "name": "Recent server", - "label": "Gateway", - "format": "{{msg.payload}}", - "layout": "col-center", - "x": 700, - "y": 340, - "wires": [] - }, - { - "id": "a15454a9.fa0948", - "type": "function", - "z": "449c1517.e25f4c", - "name": "Time", - "func": "msg.payload = new Date().toLocaleString('en-GB', {timeZone: 'Europe/Berlin'});\nreturn msg;", - "outputs": 1, - "noerr": 0, - "x": 670, - "y": 300, - "wires": [ - [ - "8712a5ac.ed18e8" - ] - ] - }, - { - "id": "a5dbb4ef.019168", - "type": "ui_text", - "z": "449c1517.e25f4c", - "group": "edb7cc8d.a3817", - "order": 1, - "width": 0, - "height": 0, - "name": "Recent Device", - "label": "Device", - "format": "{{msg.payload}}", - "layout": "col-center", - "x": 700, - "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", - "z": "", - "name": "eu.thethings.network:1883", - "broker": "eu.thethings.network", - "port": "1883", - "tls": "", - "clientid": "", - "usetls": false, - "compatmode": true, - "keepalive": "60", - "cleansession": true, - "birthTopic": "", - "birthQos": "0", - "birthPayload": "", - "closeTopic": "", - "closeQos": "0", - "closePayload": "", - "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 +[{"id":"49e3c067.e782e","type":"change","z":"449c1517.e25f4c","name":"Payload","rules":[{"t":"change","p":"topic","pt":"msg","from":"up","fromt":"str","to":"down","tot":"str"},{"t":"set","p":"payload.confirmed","pt":"msg","to":"false","tot":"bool"},{"t":"set","p":"payload.schedule","pt":"msg","to":"replace","tot":"str"},{"t":"move","p":"payload","pt":"msg","to":"payload.payload_raw","tot":"msg"},{"t":"move","p":"port","pt":"msg","to":"payload.port","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":513,"wires":[["84f1cda2.069e7"]]},{"id":"cc140589.dea168","type":"mqtt in","z":"449c1517.e25f4c","name":"listen","topic":"+/devices/+/up","qos":"2","broker":"2a15ab6f.ab2244","x":110,"y":120,"wires":[["4f97d75.6c87528"]]},{"id":"72d5e7ee.d1eba8","type":"mqtt out","z":"449c1517.e25f4c","name":"send","topic":"","qos":"","retain":"","broker":"2a15ab6f.ab2244","x":730,"y":513,"wires":[]},{"id":"4f97d75.6c87528","type":"json","z":"449c1517.e25f4c","name":"Convert","property":"payload","action":"","pretty":false,"x":260,"y":120,"wires":[["9f4b8dd3.2f0d2"]]},{"id":"9f4b8dd3.2f0d2","type":"switch","z":"449c1517.e25f4c","name":"Timeport","property":"payload.port","propertyType":"msg","rules":[{"t":"eq","v":"9","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":420,"y":120,"wires":[["8ed813a9.a9319"]]},{"id":"dac8aafa.389298","type":"json","z":"449c1517.e25f4c","name":"Convert","property":"payload","action":"","pretty":false,"x":580,"y":513,"wires":[["72d5e7ee.d1eba8"]]},{"id":"8ed813a9.a9319","type":"base64","z":"449c1517.e25f4c","name":"Decode","action":"","property":"payload.payload_raw","x":420,"y":200,"wires":[["831ab883.d6a238"]]},{"id":"84f1cda2.069e7","type":"base64","z":"449c1517.e25f4c","name":"Encode","action":"","property":"payload.payload_raw","x":420,"y":513,"wires":[["dac8aafa.389298"]]},{"id":"6190967b.01f758","type":"comment","z":"449c1517.e25f4c","name":"LoRaWAN Timeserver v1.1","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":170,"y":40,"wires":[]},{"id":"831ab883.d6a238","type":"function","z":"449c1517.e25f4c","name":"Generate Time Answer","func":"/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];","outputs":5,"noerr":0,"x":360,"y":340,"wires":[["37722d4b.08e3c2","a8a04c7a.c5fbd","a15454a9.fa0948"],["46ce842a.614d5c"],["a5dbb4ef.019168"],["1cb58e7f.221362"],["49e3c067.e782e"]],"outputLabels":["gw_eui","offset_ms","device","seq_no","time_sync_ans"]},{"id":"37722d4b.08e3c2","type":"debug","z":"449c1517.e25f4c","name":"Timeserver Gw","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":700,"y":240,"wires":[],"icon":"node-red/bridge.png"},{"id":"8712a5ac.ed18e8","type":"ui_text","z":"449c1517.e25f4c","group":"edb7cc8d.a3817","order":3,"width":0,"height":0,"name":"Recent time","label":"Last answer at:","format":"{{msg.payload}}","layout":"col-center","x":810,"y":300,"wires":[]},{"id":"46ce842a.614d5c","type":"ui_gauge","z":"449c1517.e25f4c","name":"Timeserver offset","group":"edb7cc8d.a3817","order":2,"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":710,"y":380,"wires":[]},{"id":"a8a04c7a.c5fbd","type":"ui_text","z":"449c1517.e25f4c","group":"edb7cc8d.a3817","order":1,"width":0,"height":0,"name":"Recent server","label":"Gateway","format":"{{msg.payload}}","layout":"col-center","x":700,"y":340,"wires":[]},{"id":"a15454a9.fa0948","type":"function","z":"449c1517.e25f4c","name":"Time","func":"msg.payload = new Date().toLocaleString('en-GB', {timeZone: 'Europe/Berlin'});\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":300,"wires":[["8712a5ac.ed18e8"]]},{"id":"a5dbb4ef.019168","type":"ui_text","z":"449c1517.e25f4c","group":"edb7cc8d.a3817","order":1,"width":0,"height":0,"name":"Recent Device","label":"Device","format":"{{msg.payload}}","layout":"col-center","x":700,"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","z":"","name":"eu.thethings.network:1883","broker":"eu.thethings.network","port":"1883","tls":"","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","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 diff --git a/src/lorawan.cpp b/src/lorawan.cpp index a9302432..e85c0234 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -256,16 +256,24 @@ void onEvent(ev_t ev) { sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4); if (LMIC.txrxFlags & TXRX_PORT) { // FPort -> use to switch + switch (LMIC.frame[LMIC.dataBeg - 1]) { -#if (TIME_SYNC_LORASERVER) - case TIMEPORT: // timesync answer -> call timesync processor - recv_timesync_ans(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - break; -#endif + case RCMDPORT: // opcode -> call rcommand interpreter rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); break; + default: // unknown port -> display info + +#if (TIME_SYNC_LORASERVER) + // timesync answer -> call timesync processor + if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) && + (LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) { + recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1], + LMIC.frame + LMIC.dataBeg, LMIC.dataLen); + break; + } +#endif ESP_LOGI(TAG, "Received data on unsupported port #%d", LMIC.frame[LMIC.dataBeg - 1]); break; diff --git a/src/paxcounter.conf b/src/paxcounter.conf index aa441951..add18d7f 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -91,7 +91,9 @@ #define BEACONPORT 6 // beacon alarms #define BMEPORT 7 // BME680 sensor #define BATTPORT 8 // battery voltage -#define TIMEPORT 9 // time +#define TIMEPORT 9 // time query +#define TIMEANSWERPORT_MIN 0xA0 // time answer, start of port range +#define TIMEANSWERPORT_MAX 0xDF // time answer, end of port range #define SENSOR1PORT 10 // user sensor #1 #define SENSOR2PORT 11 // user sensor #2 #define SENSOR3PORT 12 // user sensor #3 diff --git a/src/timesync.cpp b/src/timesync.cpp index 06ae9efe..e0e8ed54 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -20,7 +20,7 @@ static const char TAG[] = __FILE__; TaskHandle_t timeSyncReqTask; -static uint8_t time_sync_seqNo = 0; +static uint8_t time_sync_seqNo = TIMEANSWERPORT_MIN; static bool lora_time_sync_pending = false; typedef std::chrono::system_clock myClock; @@ -75,9 +75,6 @@ void process_timesync_req(void *taskparameter) { // enqueue timestamp samples in lora sendqueue for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { - // wrap around seqNo 0 .. 254 - time_sync_seqNo = (time_sync_seqNo < 255) ? time_sync_seqNo + 1 : 0; - // send sync request to server payload.reset(); payload.addByte(time_sync_seqNo); @@ -96,6 +93,11 @@ void process_timesync_req(void *taskparameter) { time_offset_ms += time_point_cast(time_sync_rx[k]) - time_point_cast(time_sync_tx[k]); + // wrap around seqNo keeping it in time port range + time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX) + ? time_sync_seqNo + 1 + : TIMEANSWERPORT_MIN; + if (i < TIME_SYNC_SAMPLES - 1) { // wait until next cycle vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000)); @@ -149,19 +151,21 @@ error: // called from lorawan.cpp after time_sync_req was sent void store_time_sync_req(uint32_t timestamp) { - uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES; + if (lora_time_sync_pending) { - time_sync_tx[k] += milliseconds(timestamp); + uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES; + time_sync_tx[k] += milliseconds(timestamp); - ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d", - millis() / 1000.0, time_sync_seqNo, timestamp / 1000, - timestamp % 1000); + ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d", + millis() / 1000.0, time_sync_seqNo, timestamp / 1000, + timestamp % 1000); + } } // process timeserver timestamp answer, called from lorawan.cpp -int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { +int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) { - // if no timesync handshake is pending or spurious buffer then exit + // if no timesync handshake is pending then exit if (!lora_time_sync_pending) return 0; // failure @@ -178,18 +182,18 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) { else { // we received a probably valid time frame - uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES; + uint8_t k = seq_no % TIME_SYNC_SAMPLES; uint16_t timestamp_msec; // convert 1/250th sec fractions to ms uint32_t timestamp_sec; // 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); + timestamp_sec = ((uint32_t)buf[3]) | (((uint32_t)buf[2]) << 8) | + (((uint32_t)buf[1]) << 16) | (((uint32_t)buf[0]) << 24); // the 5th byte contains the fractional seconds in 2^-8 second steps - timestamp_msec = 4 * buf[5]; + timestamp_msec = 4 * buf[4]; // construct the timepoint when message was seen on gateway time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec); From dd703a5bad33bc3f91f64bb40bdefd0887f0a935 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 18:11:28 +0200 Subject: [PATCH 4/9] code sanitzations --- include/button.h | 3 - include/irqhandler.h | 2 - src/Timeserver/Nodered-Timeserver.json | 388 ++++++++++++++++++++++++- 3 files changed, 387 insertions(+), 6 deletions(-) diff --git a/include/button.h b/include/button.h index da168520..b6951ceb 100644 --- a/include/button.h +++ b/include/button.h @@ -1,9 +1,6 @@ #ifndef _BUTTON_H #define _BUTTON_H -#include "display.h" -#include "senddata.h" - void readButton(); #endif \ No newline at end of file diff --git a/include/irqhandler.h b/include/irqhandler.h index 3b0921c1..f7e31b92 100644 --- a/include/irqhandler.h +++ b/include/irqhandler.h @@ -20,12 +20,10 @@ int mask_user_IRQ(); int unmask_user_IRQ(); #ifdef HAS_DISPLAY -#include "display.h" void IRAM_ATTR DisplayIRQ(); #endif #ifdef HAS_BUTTON -#include "button.h" void IRAM_ATTR ButtonIRQ(); #endif diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index 3d08a95b..ee46c577 100644 --- a/src/Timeserver/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -1 +1,387 @@ -[{"id":"49e3c067.e782e","type":"change","z":"449c1517.e25f4c","name":"Payload","rules":[{"t":"change","p":"topic","pt":"msg","from":"up","fromt":"str","to":"down","tot":"str"},{"t":"set","p":"payload.confirmed","pt":"msg","to":"false","tot":"bool"},{"t":"set","p":"payload.schedule","pt":"msg","to":"replace","tot":"str"},{"t":"move","p":"payload","pt":"msg","to":"payload.payload_raw","tot":"msg"},{"t":"move","p":"port","pt":"msg","to":"payload.port","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":240,"y":513,"wires":[["84f1cda2.069e7"]]},{"id":"cc140589.dea168","type":"mqtt in","z":"449c1517.e25f4c","name":"listen","topic":"+/devices/+/up","qos":"2","broker":"2a15ab6f.ab2244","x":110,"y":120,"wires":[["4f97d75.6c87528"]]},{"id":"72d5e7ee.d1eba8","type":"mqtt out","z":"449c1517.e25f4c","name":"send","topic":"","qos":"","retain":"","broker":"2a15ab6f.ab2244","x":730,"y":513,"wires":[]},{"id":"4f97d75.6c87528","type":"json","z":"449c1517.e25f4c","name":"Convert","property":"payload","action":"","pretty":false,"x":260,"y":120,"wires":[["9f4b8dd3.2f0d2"]]},{"id":"9f4b8dd3.2f0d2","type":"switch","z":"449c1517.e25f4c","name":"Timeport","property":"payload.port","propertyType":"msg","rules":[{"t":"eq","v":"9","vt":"num"}],"checkall":"true","repair":false,"outputs":1,"x":420,"y":120,"wires":[["8ed813a9.a9319"]]},{"id":"dac8aafa.389298","type":"json","z":"449c1517.e25f4c","name":"Convert","property":"payload","action":"","pretty":false,"x":580,"y":513,"wires":[["72d5e7ee.d1eba8"]]},{"id":"8ed813a9.a9319","type":"base64","z":"449c1517.e25f4c","name":"Decode","action":"","property":"payload.payload_raw","x":420,"y":200,"wires":[["831ab883.d6a238"]]},{"id":"84f1cda2.069e7","type":"base64","z":"449c1517.e25f4c","name":"Encode","action":"","property":"payload.payload_raw","x":420,"y":513,"wires":[["dac8aafa.389298"]]},{"id":"6190967b.01f758","type":"comment","z":"449c1517.e25f4c","name":"LoRaWAN Timeserver v1.1","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":170,"y":40,"wires":[]},{"id":"831ab883.d6a238","type":"function","z":"449c1517.e25f4c","name":"Generate Time Answer","func":"/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];","outputs":5,"noerr":0,"x":360,"y":340,"wires":[["37722d4b.08e3c2","a8a04c7a.c5fbd","a15454a9.fa0948"],["46ce842a.614d5c"],["a5dbb4ef.019168"],["1cb58e7f.221362"],["49e3c067.e782e"]],"outputLabels":["gw_eui","offset_ms","device","seq_no","time_sync_ans"]},{"id":"37722d4b.08e3c2","type":"debug","z":"449c1517.e25f4c","name":"Timeserver Gw","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":700,"y":240,"wires":[],"icon":"node-red/bridge.png"},{"id":"8712a5ac.ed18e8","type":"ui_text","z":"449c1517.e25f4c","group":"edb7cc8d.a3817","order":3,"width":0,"height":0,"name":"Recent time","label":"Last answer at:","format":"{{msg.payload}}","layout":"col-center","x":810,"y":300,"wires":[]},{"id":"46ce842a.614d5c","type":"ui_gauge","z":"449c1517.e25f4c","name":"Timeserver offset","group":"edb7cc8d.a3817","order":2,"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":710,"y":380,"wires":[]},{"id":"a8a04c7a.c5fbd","type":"ui_text","z":"449c1517.e25f4c","group":"edb7cc8d.a3817","order":1,"width":0,"height":0,"name":"Recent server","label":"Gateway","format":"{{msg.payload}}","layout":"col-center","x":700,"y":340,"wires":[]},{"id":"a15454a9.fa0948","type":"function","z":"449c1517.e25f4c","name":"Time","func":"msg.payload = new Date().toLocaleString('en-GB', {timeZone: 'Europe/Berlin'});\nreturn msg;","outputs":1,"noerr":0,"x":670,"y":300,"wires":[["8712a5ac.ed18e8"]]},{"id":"a5dbb4ef.019168","type":"ui_text","z":"449c1517.e25f4c","group":"edb7cc8d.a3817","order":1,"width":0,"height":0,"name":"Recent Device","label":"Device","format":"{{msg.payload}}","layout":"col-center","x":700,"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","z":"","name":"eu.thethings.network:1883","broker":"eu.thethings.network","port":"1883","tls":"","clientid":"","usetls":false,"compatmode":true,"keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","closeTopic":"","closeQos":"0","closePayload":"","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 +[ + { + "id": "49e3c067.e782e", + "type": "change", + "z": "449c1517.e25f4c", + "name": "Payload", + "rules": [ + { + "t": "change", + "p": "topic", + "pt": "msg", + "from": "up", + "fromt": "str", + "to": "down", + "tot": "str" + }, + { + "t": "set", + "p": "payload.confirmed", + "pt": "msg", + "to": "false", + "tot": "bool" + }, + { + "t": "set", + "p": "payload.schedule", + "pt": "msg", + "to": "replace", + "tot": "str" + }, + { + "t": "move", + "p": "payload", + "pt": "msg", + "to": "payload.payload_raw", + "tot": "msg" + }, + { + "t": "move", + "p": "port", + "pt": "msg", + "to": "payload.port", + "tot": "msg" + } + ], + "action": "", + "property": "", + "from": "", + "to": "", + "reg": false, + "x": 240, + "y": 513, + "wires": [ + [ + "84f1cda2.069e7" + ] + ] + }, + { + "id": "cc140589.dea168", + "type": "mqtt in", + "z": "449c1517.e25f4c", + "name": "listen", + "topic": "+/devices/+/up", + "qos": "2", + "broker": "2a15ab6f.ab2244", + "x": 110, + "y": 120, + "wires": [ + [ + "4f97d75.6c87528" + ] + ] + }, + { + "id": "72d5e7ee.d1eba8", + "type": "mqtt out", + "z": "449c1517.e25f4c", + "name": "send", + "topic": "", + "qos": "", + "retain": "", + "broker": "2a15ab6f.ab2244", + "x": 730, + "y": 513, + "wires": [] + }, + { + "id": "4f97d75.6c87528", + "type": "json", + "z": "449c1517.e25f4c", + "name": "Convert", + "property": "payload", + "action": "", + "pretty": false, + "x": 260, + "y": 120, + "wires": [ + [ + "9f4b8dd3.2f0d2" + ] + ] + }, + { + "id": "9f4b8dd3.2f0d2", + "type": "switch", + "z": "449c1517.e25f4c", + "name": "Timeport", + "property": "payload.port", + "propertyType": "msg", + "rules": [ + { + "t": "eq", + "v": "9", + "vt": "num" + } + ], + "checkall": "true", + "repair": false, + "outputs": 1, + "x": 420, + "y": 120, + "wires": [ + [ + "8ed813a9.a9319" + ] + ] + }, + { + "id": "dac8aafa.389298", + "type": "json", + "z": "449c1517.e25f4c", + "name": "Convert", + "property": "payload", + "action": "", + "pretty": false, + "x": 580, + "y": 513, + "wires": [ + [ + "72d5e7ee.d1eba8" + ] + ] + }, + { + "id": "8ed813a9.a9319", + "type": "base64", + "z": "449c1517.e25f4c", + "name": "Decode", + "action": "", + "property": "payload.payload_raw", + "x": 420, + "y": 200, + "wires": [ + [ + "831ab883.d6a238" + ] + ] + }, + { + "id": "84f1cda2.069e7", + "type": "base64", + "z": "449c1517.e25f4c", + "name": "Encode", + "action": "", + "property": "payload.payload_raw", + "x": 420, + "y": 513, + "wires": [ + [ + "dac8aafa.389298" + ] + ] + }, + { + "id": "6190967b.01f758", + "type": "comment", + "z": "449c1517.e25f4c", + "name": "LoRaWAN Timeserver v1.1", + "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": 170, + "y": 40, + "wires": [] + }, + { + "id": "831ab883.d6a238", + "type": "function", + "z": "449c1517.e25f4c", + "name": "Generate Time Answer", + "func": "/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", + "outputs": 5, + "noerr": 0, + "x": 360, + "y": 340, + "wires": [ + [ + "37722d4b.08e3c2", + "a8a04c7a.c5fbd", + "a15454a9.fa0948" + ], + [ + "46ce842a.614d5c" + ], + [ + "a5dbb4ef.019168" + ], + [ + "1cb58e7f.221362" + ], + [ + "49e3c067.e782e" + ] + ], + "outputLabels": [ + "gw_eui", + "offset_ms", + "device", + "seq_no", + "time_sync_ans" + ] + }, + { + "id": "37722d4b.08e3c2", + "type": "debug", + "z": "449c1517.e25f4c", + "name": "Timeserver Gw", + "active": true, + "tosidebar": false, + "console": false, + "tostatus": true, + "complete": "payload", + "x": 700, + "y": 240, + "wires": [], + "icon": "node-red/bridge.png" + }, + { + "id": "8712a5ac.ed18e8", + "type": "ui_text", + "z": "449c1517.e25f4c", + "group": "edb7cc8d.a3817", + "order": 3, + "width": 0, + "height": 0, + "name": "Recent time", + "label": "Last answer at:", + "format": "{{msg.payload}}", + "layout": "col-center", + "x": 810, + "y": 300, + "wires": [] + }, + { + "id": "46ce842a.614d5c", + "type": "ui_gauge", + "z": "449c1517.e25f4c", + "name": "Timeserver offset", + "group": "edb7cc8d.a3817", + "order": 2, + "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": 710, + "y": 380, + "wires": [] + }, + { + "id": "a8a04c7a.c5fbd", + "type": "ui_text", + "z": "449c1517.e25f4c", + "group": "edb7cc8d.a3817", + "order": 1, + "width": 0, + "height": 0, + "name": "Recent server", + "label": "Gateway", + "format": "{{msg.payload}}", + "layout": "col-center", + "x": 700, + "y": 340, + "wires": [] + }, + { + "id": "a15454a9.fa0948", + "type": "function", + "z": "449c1517.e25f4c", + "name": "Time", + "func": "msg.payload = new Date().toLocaleString('en-GB', {timeZone: 'Europe/Berlin'});\nreturn msg;", + "outputs": 1, + "noerr": 0, + "x": 670, + "y": 300, + "wires": [ + [ + "8712a5ac.ed18e8" + ] + ] + }, + { + "id": "a5dbb4ef.019168", + "type": "ui_text", + "z": "449c1517.e25f4c", + "group": "edb7cc8d.a3817", + "order": 1, + "width": 0, + "height": 0, + "name": "Recent Device", + "label": "Device", + "format": "{{msg.payload}}", + "layout": "col-center", + "x": 700, + "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", + "z": "", + "name": "eu.thethings.network:1883", + "broker": "eu.thethings.network", + "port": "1883", + "tls": "", + "clientid": "", + "usetls": false, + "compatmode": true, + "keepalive": "60", + "cleansession": true, + "birthTopic": "", + "birthQos": "0", + "birthPayload": "", + "closeTopic": "", + "closeQos": "0", + "closePayload": "", + "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 964be4d1fd0a3b1d8e3fa8d1f2848385614f47eb Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 19:46:00 +0200 Subject: [PATCH 5/9] Timeserver logic port9 fix --- src/Timeserver/Nodered-Timeserver.json | 12 ++++++------ src/rcommand.cpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index ee46c577..334151f7 100644 --- a/src/Timeserver/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -149,8 +149,8 @@ "name": "Decode", "action": "", "property": "payload.payload_raw", - "x": 420, - "y": 200, + "x": 580, + "y": 120, "wires": [ [ "831ab883.d6a238" @@ -186,12 +186,12 @@ "id": "831ab883.d6a238", "type": "function", "z": "449c1517.e25f4c", - "name": "Generate Time Answer", - "func": "/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", + "name": "Timeserver Logic", + "func": "/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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\n// guess if we have received a valid time_sync_req command\nif (msg.payload.payload_raw.length != 1)\n return;\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar seqNo = msg.payload.payload_raw[0];\n//var seqNoMsg = { payload: seqNo };\nvar seqNoMsg = { payload: msg.payload.payload_raw.length };\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", "outputs": 5, "noerr": 0, - "x": 360, - "y": 340, + "x": 350, + "y": 320, "wires": [ [ "37722d4b.08e3c2", diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 8e43e24d..2ab24ce2 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -278,7 +278,7 @@ void get_time(uint8_t val[]) { payload.reset(); payload.addTime(now()); payload.addByte(timeStatus() << 4 | timeSource); - SendPayload(STATUSPORT, prio_high); + SendPayload(TIMEPORT, prio_high); }; void set_time(uint8_t val[]) { From bf8ce227ff17bdf8af8322377b485a2e319148e8 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 19:58:45 +0200 Subject: [PATCH 6/9] Nodered Timeserver bugfix --- src/Timeserver/Nodered-Timeserver.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index 334151f7..85e52607 100644 --- a/src/Timeserver/Nodered-Timeserver.json +++ b/src/Timeserver/Nodered-Timeserver.json @@ -187,7 +187,7 @@ "type": "function", "z": "449c1517.e25f4c", "name": "Timeserver Logic", - "func": "/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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\n// guess if we have received a valid time_sync_req command\nif (msg.payload.payload_raw.length != 1)\n return;\n\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar seqNo = msg.payload.payload_raw[0];\n//var seqNoMsg = { payload: seqNo };\nvar seqNoMsg = { payload: msg.payload.payload_raw.length };\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", + "func": "/* LoRaWAN Timeserver\n\nconstruct 5 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\nFPort = sequence number (taken from node's time_sync_req)\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\n// guess if we have received a valid time_sync_req command\nif (msg.payload.payload_raw.length != 1)\n return;\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(5);\nnew DataView(buf).setUint32(0, seconds);\nnew DataView(buf).setUint8(4, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = seqNo;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", "outputs": 5, "noerr": 0, "x": 350, From 261c643d7f6772d0c84c09c2c8d639e6d142798b Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 20:23:28 +0200 Subject: [PATCH 7/9] fix BME280 no iaq value --- README.md | 4 ++-- src/bmesensor.cpp | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5e50263f..a74024dd 100644 --- a/README.md +++ b/README.md @@ -387,9 +387,9 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts. Device answers with it's current status on Port 4. -0x85 get BME680 sensor data +0x85 get BME280 / BME680 sensor data - Device answers with BME680 sensor data set on Port 7. + Device answers with BME sensor data set on Port 7. 0x86 get time/date diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 19111d32..4a3b0250 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -165,6 +165,7 @@ void bme_loop(void *pvParameters) { (bme.readPressure() / 100.0); // conversion Pa -> hPa // bme.readAltitude(SEALEVELPRESSURE_HPA); bme_status.humidity = bme.readHumidity(); + bme_status.iaq = 0; // IAQ feature not present with BME280 I2C_MUTEX_UNLOCK(); } } From 11c2fb6fa2749313f4cc740c6b8b952c77d3147c Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 21:10:33 +0200 Subject: [PATCH 8/9] TTN decoder updates --- src/TTN/packed_decoder.js | 14 +++++++------- src/TTN/plain_decoder.js | 33 +++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/TTN/packed_decoder.js b/src/TTN/packed_decoder.js index 79b2def4..f1bf2c73 100644 --- a/src/TTN/packed_decoder.js +++ b/src/TTN/packed_decoder.js @@ -5,7 +5,7 @@ function Decoder(bytes, port) { var decoded = {}; - if (bytes.length === 0) { + if (bytes.length === 0) { return {}; } @@ -33,10 +33,6 @@ function Decoder(bytes, port) { if (bytes.length === 17) { return decode(bytes, [uint16, uptime, uint8, uint32, uint8, uint8], ['voltage', 'uptime', 'cputemp', 'memory', 'reset0', 'reset1']); } - // epoch time answer - if (bytes.length === 5) { - return decode(bytes, [uint32, uint8], ['time', 'timestatus']); - } } if (port === 3) { @@ -72,9 +68,13 @@ function Decoder(bytes, port) { if (port === 9) { // timesync request if (bytes.length === 1) { - decoded.timesync_seqno = bytes[0]; + decoded.timesync_seqno = bytes[0]; + return decoded; + } + // epoch time answer + if (bytes.length === 5) { + return decode(bytes, [uint32, uint8], ['time', 'timestatus']); } - return decoded; } } diff --git a/src/TTN/plain_decoder.js b/src/TTN/plain_decoder.js index 62249d08..d87fcb9e 100644 --- a/src/TTN/plain_decoder.js +++ b/src/TTN/plain_decoder.js @@ -8,10 +8,12 @@ function Decoder(bytes, port) { var i = 0; if (bytes.length >= 2) { - decoded.wifi = (bytes[i++] << 8) | bytes[i++];} - + decoded.wifi = (bytes[i++] << 8) | bytes[i++]; + } + if (bytes.length === 4 || bytes.length > 15) { - decoded.ble = (bytes[i++] << 8) | bytes[i++];} + decoded.ble = (bytes[i++] << 8) | bytes[i++]; + } if (bytes.length > 4) { decoded.latitude = ((bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++]); @@ -52,7 +54,7 @@ function Decoder(bytes, port) { decoded.rssi = bytes[i++]; decoded.beacon = bytes[i++]; } - + if (port === 7) { var i = 0; decoded.temperature = ((bytes[i++] << 8) | bytes[i++]); @@ -60,19 +62,26 @@ function Decoder(bytes, port) { decoded.humidity = ((bytes[i++] << 8) | bytes[i++]); decoded.air = ((bytes[i++] << 8) | bytes[i++]); } - + if (port === 8) { var i = 0; if (bytes.length >= 2) { - decoded.battery = (bytes[i++] << 8) | bytes[i++];} - } - - if (port === 9) { - if (bytes.length === 1) { - decoded.timesync_seqno = bytes[0]; + decoded.battery = (bytes[i++] << 8) | bytes[i++]; } } - return decoded; + if (port === 9) { + // timesync request + if (bytes.length === 1) { + decoded.timesync_seqno = bytes[0]; + } + // epoch time answer + if (bytes.length === 5) { + var i = 0; + decoded.time = ((bytes[i++] << 24) | (bytes[i++] << 16) | (bytes[i++] << 8) | bytes[i++]); + decoded.timestatus = bytes[i++]; + } + return decoded; + } } From 20dafae7464cf497e9a851c052d8fea6246a5298 Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 6 Apr 2019 21:54:05 +0200 Subject: [PATCH 9/9] v1.7.5 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 74bc9871..4722927b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -31,7 +31,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.422 +release_version = 1.7.5 ; 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 debug_level = 3