commit
e0c82d79a5
@ -54,7 +54,7 @@ Depending on board hardware following features are supported:
|
|||||||
- LED (shows power & status)
|
- LED (shows power & status)
|
||||||
- OLED Display (shows detailed status)
|
- OLED Display (shows detailed status)
|
||||||
- RGB LED (shows colorized status)
|
- RGB LED (shows colorized status)
|
||||||
- Button (used to flip display pages if device has display, else sends alarm message)
|
- Button (short press: flip display page / long press: send alarm message)
|
||||||
- Silicon unique ID
|
- Silicon unique ID
|
||||||
- Battery voltage monitoring
|
- Battery voltage monitoring
|
||||||
- GPS (Generic serial NMEA, or Quectel L76 I2C)
|
- GPS (Generic serial NMEA, or Quectel L76 I2C)
|
||||||
|
@ -5,12 +5,11 @@
|
|||||||
#include "irqhandler.h"
|
#include "irqhandler.h"
|
||||||
#include "timekeeper.h"
|
#include "timekeeper.h"
|
||||||
|
|
||||||
#define TIME_SYNC_FRAME_LENGTH 0x07 // timeserver answer frame length [bytes]
|
#define TIME_SYNC_FRAME_LENGTH 6 // timeserver answer frame length [bytes]
|
||||||
#define TIME_SYNC_FIXUP 16 // compensation for processing time [milliseconds]
|
#define TIME_SYNC_FIXUP 16 // compensation for processing time [milliseconds]
|
||||||
#define TIMEREQUEST_MAX_SEQNO 0xfe // threshold for wrap around seqno
|
#define TIMEREQUEST_MAX_SEQNO 0xfe // threshold for wrap around seqNo
|
||||||
#define TIMEREQUEST_FINISH \
|
#define TIMEREQUEST_END (TIMEREQUEST_MAX_SEQNO + 1) // end of handshake marker
|
||||||
(TIMEREQUEST_MAX_SEQNO + 1) // marker for end of timesync handshake
|
#define GPS_UTC_DIFF 315964800 // seconds diff between gps and utc epoch
|
||||||
#define GPS_UTC_DIFF 315964800
|
|
||||||
|
|
||||||
enum timesync_t {
|
enum timesync_t {
|
||||||
timesync_tx,
|
timesync_tx,
|
||||||
@ -24,11 +23,6 @@ void timesync_init(void);
|
|||||||
void timesync_sendReq(void);
|
void timesync_sendReq(void);
|
||||||
void timesync_storeReq(uint32_t timestamp, timesync_t timestamp_type);
|
void timesync_storeReq(uint32_t timestamp, timesync_t timestamp_type);
|
||||||
void IRAM_ATTR timesync_processReq(void *taskparameter);
|
void IRAM_ATTR timesync_processReq(void *taskparameter);
|
||||||
|
void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag);
|
||||||
#if (TIME_SYNC_LORASERVER)
|
|
||||||
int recv_timeserver_ans(const uint8_t buf[], uint8_t buf_len);
|
|
||||||
#elif (TIME_SYNC_LORAWAN)
|
|
||||||
void IRAM_ATTR DevTimeAns_Cb(void *pUserData, int flagSuccess);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -45,7 +45,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I
|
|||||||
|
|
||||||
[common]
|
[common]
|
||||||
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
||||||
release_version = 1.9.94
|
release_version = 1.9.95
|
||||||
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
|
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
|
||||||
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
|
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
|
||||||
debug_level = 3
|
debug_level = 3
|
||||||
|
@ -1,93 +1,10 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"id": "9c105726.613a58",
|
|
||||||
"type": "mqtt in",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "listen",
|
|
||||||
"topic": "+/devices/+/up",
|
|
||||||
"qos": "2",
|
|
||||||
"broker": "2a15ab6f.ab2244",
|
|
||||||
"x": 90,
|
|
||||||
"y": 127,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"113ef524.57edeb"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "113ef524.57edeb",
|
|
||||||
"type": "json",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "Convert",
|
|
||||||
"property": "payload",
|
|
||||||
"action": "",
|
|
||||||
"pretty": false,
|
|
||||||
"x": 240,
|
|
||||||
"y": 127,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"120561a.088359e"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "120561a.088359e",
|
|
||||||
"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": 400,
|
|
||||||
"y": 127,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"d6f27e8e.93242"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "d6f27e8e.93242",
|
|
||||||
"type": "base64",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "Decode",
|
|
||||||
"action": "",
|
|
||||||
"property": "payload.payload_raw",
|
|
||||||
"x": 560,
|
|
||||||
"y": 127,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"b8bd33fd.61caa",
|
|
||||||
"cc245719.3c4cd8"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "15980d22.6f4663",
|
|
||||||
"type": "comment",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "LoRaWAN Timeserver v1.3",
|
|
||||||
"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": 150,
|
|
||||||
"y": 47,
|
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "b8bd33fd.61caa",
|
"id": "b8bd33fd.61caa",
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "449c1517.e25f4c",
|
"z": "449c1517.e25f4c",
|
||||||
"name": "Timeserver Logic",
|
"name": "Timeserver Logic",
|
||||||
"func": "/* LoRaWAN Timeserver\n\nVERSION: 1.3\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1 sequence number (taken from node's time_sync_req)\n2..5 current second (from GPS epoch starting 1980)\n6 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 = 1000; // 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 msg.port = 9; // Paxcounter TIMEPORT\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);\n// Timezone (in 15min steps) -> deprecated\n//var timezone = 8; // CET = UTC+2h\n//new DataView(buf).setUint8(1, timezone);\nnew DataView(buf).setUint32(2, seconds);\nnew DataView(buf).setUint8(6, fractions);\n\nmsg.payload = new Buffer(new Uint8Array(buf));\nmsg.port = 9; // Paxcounter TIMEPORT\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];",
|
"func": "/* LoRaWAN Timeserver\n\nVERSION: 1.3\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n1 sequence number (taken from node's time_sync_req)\n2..5 current second (from GPS epoch starting 1980)\n6 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 = 1000; // 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 msg.port = 9; // Paxcounter TIMEPORT\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));\nmsg.port = 9; // Paxcounter TIMEPORT\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];",
|
||||||
"outputs": 5,
|
"outputs": 5,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"x": 330,
|
"x": 330,
|
||||||
@ -119,83 +36,6 @@
|
|||||||
"time_sync_ans"
|
"time_sync_ans"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "c9a83ac9.50fd18",
|
|
||||||
"type": "debug",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "Timeserver Gw",
|
|
||||||
"active": true,
|
|
||||||
"tosidebar": false,
|
|
||||||
"console": false,
|
|
||||||
"tostatus": true,
|
|
||||||
"complete": "payload",
|
|
||||||
"x": 680,
|
|
||||||
"y": 247,
|
|
||||||
"wires": [],
|
|
||||||
"icon": "node-red/bridge.png"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "247204ab.a9f83c",
|
|
||||||
"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": 790,
|
|
||||||
"y": 307,
|
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6aeb3720.a89618",
|
|
||||||
"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": 680,
|
|
||||||
"y": 347,
|
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "6ac55bbe.12ac54",
|
|
||||||
"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": 650,
|
|
||||||
"y": 307,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"247204ab.a9f83c"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "cc245719.3c4cd8",
|
|
||||||
"type": "debug",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "",
|
|
||||||
"active": true,
|
|
||||||
"tosidebar": true,
|
|
||||||
"console": false,
|
|
||||||
"tostatus": false,
|
|
||||||
"complete": "true",
|
|
||||||
"x": 860,
|
|
||||||
"y": 140,
|
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "9b4f492d.fbfd18",
|
"id": "9b4f492d.fbfd18",
|
||||||
"type": "change",
|
"type": "change",
|
||||||
@ -253,6 +93,22 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "9c105726.613a58",
|
||||||
|
"type": "mqtt in",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "listen",
|
||||||
|
"topic": "+/devices/+/up",
|
||||||
|
"qos": "2",
|
||||||
|
"broker": "2a15ab6f.ab2244",
|
||||||
|
"x": 90,
|
||||||
|
"y": 127,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"113ef524.57edeb"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "1c9a7438.6e38ec",
|
"id": "1c9a7438.6e38ec",
|
||||||
"type": "mqtt out",
|
"type": "mqtt out",
|
||||||
@ -266,6 +122,47 @@
|
|||||||
"y": 520,
|
"y": 520,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "113ef524.57edeb",
|
||||||
|
"type": "json",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "Convert",
|
||||||
|
"property": "payload",
|
||||||
|
"action": "",
|
||||||
|
"pretty": false,
|
||||||
|
"x": 240,
|
||||||
|
"y": 127,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"120561a.088359e"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "120561a.088359e",
|
||||||
|
"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": 400,
|
||||||
|
"y": 127,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"d6f27e8e.93242"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "90e76b02.6298f8",
|
"id": "90e76b02.6298f8",
|
||||||
"type": "json",
|
"type": "json",
|
||||||
@ -282,6 +179,22 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "d6f27e8e.93242",
|
||||||
|
"type": "base64",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "Decode",
|
||||||
|
"action": "",
|
||||||
|
"property": "payload.payload_raw",
|
||||||
|
"x": 560,
|
||||||
|
"y": 127,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"b8bd33fd.61caa",
|
||||||
|
"cc245719.3c4cd8"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "53a85e2c.2728d",
|
"id": "53a85e2c.2728d",
|
||||||
"type": "base64",
|
"type": "base64",
|
||||||
@ -297,6 +210,47 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "15980d22.6f4663",
|
||||||
|
"type": "comment",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "LoRaWAN Timeserver v1.3",
|
||||||
|
"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": 150,
|
||||||
|
"y": 47,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "c9a83ac9.50fd18",
|
||||||
|
"type": "debug",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "Timeserver Gw",
|
||||||
|
"active": true,
|
||||||
|
"tosidebar": false,
|
||||||
|
"console": false,
|
||||||
|
"tostatus": true,
|
||||||
|
"complete": "payload",
|
||||||
|
"x": 680,
|
||||||
|
"y": 247,
|
||||||
|
"wires": [],
|
||||||
|
"icon": "node-red/bridge.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "247204ab.a9f83c",
|
||||||
|
"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": 790,
|
||||||
|
"y": 307,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "de908e66.b6fd3",
|
"id": "de908e66.b6fd3",
|
||||||
"type": "ui_gauge",
|
"type": "ui_gauge",
|
||||||
@ -323,6 +277,38 @@
|
|||||||
"y": 387,
|
"y": 387,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "6aeb3720.a89618",
|
||||||
|
"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": 680,
|
||||||
|
"y": 347,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "6ac55bbe.12ac54",
|
||||||
|
"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": 650,
|
||||||
|
"y": 307,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"247204ab.a9f83c"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "d5a35bab.44cb18",
|
"id": "d5a35bab.44cb18",
|
||||||
"type": "ui_text",
|
"type": "ui_text",
|
||||||
@ -355,6 +341,20 @@
|
|||||||
"y": 467,
|
"y": 467,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "cc245719.3c4cd8",
|
||||||
|
"type": "debug",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "",
|
||||||
|
"active": false,
|
||||||
|
"tosidebar": true,
|
||||||
|
"console": false,
|
||||||
|
"tostatus": false,
|
||||||
|
"complete": "true",
|
||||||
|
"x": 860,
|
||||||
|
"y": 140,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "2a15ab6f.ab2244",
|
"id": "2a15ab6f.ab2244",
|
||||||
"type": "mqtt-broker",
|
"type": "mqtt-broker",
|
||||||
|
@ -75,11 +75,8 @@ var fractions = (timestamp % 1000) / 4;
|
|||||||
|
|
||||||
let buf = new ArrayBuffer(6);
|
let buf = new ArrayBuffer(6);
|
||||||
new DataView(buf).setUint8(0, seqNo);
|
new DataView(buf).setUint8(0, seqNo);
|
||||||
// Timezone (in 15min steps) -> deprecated
|
new DataView(buf).setUint32(1, seconds);
|
||||||
//var timezone = 8; // CET = UTC+2h
|
new DataView(buf).setUint8(5, fractions);
|
||||||
//new DataView(buf).setUint8(1, timezone);
|
|
||||||
new DataView(buf).setUint32(2, seconds);
|
|
||||||
new DataView(buf).setUint8(6, fractions);
|
|
||||||
|
|
||||||
msg.payload = new Buffer(new Uint8Array(buf));
|
msg.payload = new Buffer(new Uint8Array(buf));
|
||||||
msg.port = 9; // Paxcounter TIMEPORT
|
msg.port = 9; // Paxcounter TIMEPORT
|
||||||
|
@ -471,10 +471,8 @@ void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
|
|||||||
// timeserver answer -> call timesync processor
|
// timeserver answer -> call timesync processor
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER)
|
||||||
case TIMEPORT:
|
case TIMEPORT:
|
||||||
// store LMIC time when we received the timesync answer
|
|
||||||
timesync_storeReq(osticks2ms(os_getTime()), timesync_rx);
|
|
||||||
// get and store gwtime from payload
|
// get and store gwtime from payload
|
||||||
recv_timeserver_ans(pMsg, nMsg);
|
timesync_serverAnswer(const_cast<uint8_t *>(pMsg), nMsg);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// decode any piggybacked downlink MAC commands if we want to print those
|
// decode any piggybacked downlink MAC commands if we want to print those
|
||||||
|
@ -33,7 +33,7 @@ IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
|
|||||||
|
|
||||||
lmictask 1 2 MCCI LMiC LORAWAN stack
|
lmictask 1 2 MCCI LMiC LORAWAN stack
|
||||||
clockloop 1 4 generates realtime telegrams for external clock
|
clockloop 1 4 generates realtime telegrams for external clock
|
||||||
timesync_req 1 3 processes realtime time sync requests
|
timesync_proc 1 3 processes realtime time sync requests
|
||||||
irqhandler 1 1 cyclic tasks (i.e. displayrefresh) triggered by timers
|
irqhandler 1 1 cyclic tasks (i.e. displayrefresh) triggered by timers
|
||||||
gpsloop 1 1 reads data from GPS via serial or i2c
|
gpsloop 1 1 reads data from GPS via serial or i2c
|
||||||
lorasendtask 1 1 feeds data from lora sendqueue to lmcic
|
lorasendtask 1 1 feeds data from lora sendqueue to lmcic
|
||||||
|
@ -33,27 +33,33 @@ void calibrateTime(void) {
|
|||||||
timesync_sendReq();
|
timesync_sendReq();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// only if we lost time, we try to fallback to local time source RTS or GPS
|
||||||
|
if (timeSource == _unsynced) {
|
||||||
|
|
||||||
// has RTC -> fallback to RTC time
|
// has RTC -> fallback to RTC time
|
||||||
#ifdef HAS_RTC
|
#ifdef HAS_RTC
|
||||||
t = get_rtctime();
|
t = get_rtctime();
|
||||||
if (t) {
|
if (t) {
|
||||||
timeSource = _rtc;
|
timeSource = _rtc;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// no RTC -> fallback to GPS time
|
// no RTC -> fallback to GPS time
|
||||||
#if (HAS_GPS)
|
#if (HAS_GPS)
|
||||||
// fetch recent time from last NMEA record
|
// fetch recent time from last NMEA record
|
||||||
t = fetch_gpsTime(&t_msec);
|
t = fetch_gpsTime(&t_msec);
|
||||||
if (t) {
|
if (t) {
|
||||||
timeSource = _gps;
|
timeSource = _gps;
|
||||||
goto finish;
|
goto finish;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
} // fallback
|
||||||
|
|
||||||
// no local time source -> don't set time
|
else
|
||||||
return;
|
|
||||||
|
// no fallback time source available -> we can't set time
|
||||||
|
return;
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
|
|
||||||
@ -82,7 +88,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
|
|||||||
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
|
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0,
|
ESP_LOGD(TAG, "[%0.3f] UTC time: %d.%03d sec", millis() / 1000.0,
|
||||||
time_to_set, t_msec % 1000);
|
time_to_set, t_msec % 1000);
|
||||||
|
|
||||||
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
|
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
|
||||||
|
252
src/timesync.cpp
252
src/timesync.cpp
@ -3,11 +3,13 @@
|
|||||||
///--> IMPORTANT LICENSE NOTE for timesync option 1 in this file <--///
|
///--> IMPORTANT LICENSE NOTE for timesync option 1 in this file <--///
|
||||||
|
|
||||||
PLEASE NOTE: There is a patent filed for the time sync algorithm used in the
|
PLEASE NOTE: There is a patent filed for the time sync algorithm used in the
|
||||||
code of this file. The shown implementation example is covered by the
|
code of this file for timesync option TIME_SYNC_LORASERVER. The shown
|
||||||
repository's licencse, but you may not be eligible to deploy the applied
|
implementation example is covered by the repository's licencse, but you may not
|
||||||
algorithm in applications without granted license by the patent holder.
|
be eligible to deploy the applied algorithm in applications without granted
|
||||||
|
license by the patent holder.
|
||||||
|
|
||||||
You may use timesync option 2 if you do not want or cannot accept this.
|
You may use timesync option TIME_SYNC_LORAWAN if you do not want or cannot
|
||||||
|
accept this.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -27,19 +29,17 @@ static const char TAG[] = __FILE__;
|
|||||||
static bool timeSyncPending = false;
|
static bool timeSyncPending = false;
|
||||||
static uint8_t time_sync_seqNo = (uint8_t)random(TIMEREQUEST_MAX_SEQNO),
|
static uint8_t time_sync_seqNo = (uint8_t)random(TIMEREQUEST_MAX_SEQNO),
|
||||||
sample_idx;
|
sample_idx;
|
||||||
static uint16_t timestamp_msec;
|
static uint32_t timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps];
|
||||||
static uint32_t timestamp_sec,
|
static TaskHandle_t timeSyncProcTask = NULL;
|
||||||
timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps];
|
|
||||||
static TaskHandle_t timeSyncReqTask = NULL;
|
|
||||||
|
|
||||||
// create task for timeserver handshake processing, called from main.cpp
|
// create task for timeserver handshake processing, called from main.cpp
|
||||||
void timesync_init() {
|
void timesync_init() {
|
||||||
xTaskCreatePinnedToCore(timesync_processReq, // task function
|
xTaskCreatePinnedToCore(timesync_processReq, // task function
|
||||||
"timesync_req", // name of task
|
"timesync_proc", // name of task
|
||||||
2048, // stack size of task
|
2048, // stack size of task
|
||||||
(void *)1, // task parameter
|
(void *)1, // task parameter
|
||||||
3, // priority of the task
|
3, // priority of the task
|
||||||
&timeSyncReqTask, // task handle
|
&timeSyncProcTask, // task handle
|
||||||
1); // CPU core
|
1); // CPU core
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,19 +53,19 @@ void timesync_sendReq(void) {
|
|||||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request seqNo#%d started",
|
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request seqNo#%d started",
|
||||||
millis() / 1000.0, time_sync_seqNo);
|
millis() / 1000.0, time_sync_seqNo);
|
||||||
sample_idx = 0;
|
sample_idx = 0;
|
||||||
xTaskNotifyGive(timeSyncReqTask);
|
xTaskNotifyGive(timeSyncProcTask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// task for processing time sync request
|
// task for processing time sync request
|
||||||
void IRAM_ATTR timesync_processReq(void *taskparameter) {
|
void IRAM_ATTR timesync_processReq(void *taskparameter) {
|
||||||
|
|
||||||
uint32_t rcv_seq_no = TIMEREQUEST_FINISH, time_offset_ms;
|
uint32_t seqNo = TIMEREQUEST_END, time_offset_ms;
|
||||||
|
|
||||||
// this task is an endless loop, waiting in blocked mode, until it is
|
// this task is an endless loop, waiting in blocked mode, until it is
|
||||||
// unblocked by timesync_sendReq(). It then waits to be notified from
|
// unblocked by timesync_sendReq(). It then waits to be notified from
|
||||||
// recv_timesync_ans(), which is called from RX callback in lorawan.cpp, each
|
// timesync_serverAnswer(), which is called from LMIC each time a timestamp
|
||||||
// time a timestamp from timeserver arrived.
|
// from the timesource via LORAWAN arrived.
|
||||||
|
|
||||||
// --- asnychronous part: generate and collect timestamps from gateway ---
|
// --- asnychronous part: generate and collect timestamps from gateway ---
|
||||||
|
|
||||||
@ -89,50 +89,46 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) {
|
|||||||
|
|
||||||
// send timesync request to timeserver or networkserver
|
// send timesync request to timeserver or networkserver
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER)
|
||||||
// timesync option 1: use external timeserver (for LoRAWAN < 1.0.3)
|
// ask user's timeserver (for LoRAWAN < 1.0.3)
|
||||||
payload.reset();
|
payload.reset();
|
||||||
payload.addByte(time_sync_seqNo);
|
payload.addByte(time_sync_seqNo);
|
||||||
SendPayload(TIMEPORT, prio_high);
|
SendPayload(TIMEPORT, prio_high);
|
||||||
#elif (TIME_SYNC_LORAWAN)
|
#elif (TIME_SYNC_LORAWAN)
|
||||||
// timesync option 2: use LoRAWAN network time (requires LoRAWAN >= 1.0.3)
|
// ask network (requires LoRAWAN >= 1.0.3)
|
||||||
LMIC_requestNetworkTime(DevTimeAns_Cb, &time_sync_seqNo);
|
LMIC_requestNetworkTime(timesync_serverAnswer, &time_sync_seqNo);
|
||||||
// open a receive window to trigger DevTimeAns
|
// open a receive window to immediately get DevTimeAns
|
||||||
LMIC_sendAlive();
|
LMIC_sendAlive();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// open a receive window to immediately get the answer (Class A device)
|
||||||
|
// LMIC_sendAlive();
|
||||||
|
|
||||||
// wait until a timestamp was received
|
// wait until a timestamp was received
|
||||||
while (rcv_seq_no != time_sync_seqNo) {
|
if (xTaskNotifyWait(0x00, ULONG_MAX, &seqNo,
|
||||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &rcv_seq_no,
|
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) {
|
||||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
|
ESP_LOGW(TAG, "[%0.3f] Timesync aborted: timed out", millis() / 1000.0);
|
||||||
pdFALSE) {
|
goto Fail; // no valid sequence received before timeout
|
||||||
ESP_LOGW(TAG, "[%0.3f] Timesync handshake error: timeout",
|
|
||||||
millis() / 1000.0);
|
|
||||||
goto finish; // no valid sequence received before timeout
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate time diff from received timestamp
|
// check if we are in handshake with server
|
||||||
|
if (seqNo != time_sync_seqNo) {
|
||||||
|
ESP_LOGW(TAG, "[%0.3f] Timesync aborted: handshake out of sync",
|
||||||
|
millis() / 1000.0);
|
||||||
|
goto Fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate time diff with received timestamp
|
||||||
time_offset_ms += timesync_timestamp[sample_idx][timesync_rx] -
|
time_offset_ms += timesync_timestamp[sample_idx][timesync_rx] -
|
||||||
timesync_timestamp[sample_idx][timesync_tx];
|
timesync_timestamp[sample_idx][timesync_tx];
|
||||||
|
|
||||||
// increment and maybe wrap around seqNo, keeping it in time port range
|
// increment and wrap around seqNo, keeping it in time port range
|
||||||
WRAP(time_sync_seqNo, TIMEREQUEST_MAX_SEQNO);
|
WRAP(time_sync_seqNo, TIMEREQUEST_MAX_SEQNO);
|
||||||
// increment index for timestamp array
|
// increment index for timestamp array
|
||||||
sample_idx++;
|
sample_idx++;
|
||||||
|
|
||||||
// if last cycle, finish after, else pause until next cycle
|
// if last cycle, finish after, else pause until next cycle
|
||||||
if (i < TIME_SYNC_SAMPLES - 1) { // wait for next cycle
|
if (i < TIME_SYNC_SAMPLES - 1)
|
||||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||||
} else {
|
|
||||||
#if (TIME_SYNC_LORASERVER)
|
|
||||||
// send finish char for closing timesync handshake
|
|
||||||
payload.reset();
|
|
||||||
payload.addByte(TIMEREQUEST_FINISH);
|
|
||||||
SendPayload(RCMDPORT, prio_high);
|
|
||||||
// open a receive window to get last time_sync_answer instantly
|
|
||||||
LMIC_sendAlive();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end of for loop to collect timestamp samples
|
} // end of for loop to collect timestamp samples
|
||||||
|
|
||||||
@ -154,12 +150,20 @@ void IRAM_ATTR timesync_processReq(void *taskparameter) {
|
|||||||
time_offset_ms / 1000,
|
time_offset_ms / 1000,
|
||||||
time_offset_ms % 1000, _lora);
|
time_offset_ms % 1000, _lora);
|
||||||
|
|
||||||
|
// send timerequest end char to show timesync was successful
|
||||||
|
payload.reset();
|
||||||
|
payload.addByte(TIMEREQUEST_END);
|
||||||
|
SendPayload(RCMDPORT, prio_high);
|
||||||
|
goto Finish;
|
||||||
|
|
||||||
|
Fail:
|
||||||
|
// set retry timer
|
||||||
|
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
|
||||||
|
|
||||||
|
Finish:
|
||||||
// end of time critical section: release app irq lock
|
// end of time critical section: release app irq lock
|
||||||
unmask_user_IRQ();
|
unmask_user_IRQ();
|
||||||
|
|
||||||
finish:
|
|
||||||
timeSyncPending = false;
|
|
||||||
|
|
||||||
} // infinite while(1)
|
} // infinite while(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,86 +176,65 @@ void timesync_storeReq(uint32_t timestamp, timesync_t timestamp_type) {
|
|||||||
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
|
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER)
|
// callback function to receive network time server answer
|
||||||
// evaluate timerserver's timestamp answer, called by myRxCallback() in
|
void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag) {
|
||||||
// lorawan.cpp
|
|
||||||
int recv_timeserver_ans(const uint8_t buf[], const uint8_t buf_len) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
parse 6 byte timesync_answer:
|
|
||||||
|
|
||||||
byte meaning
|
|
||||||
1 sequence number (taken from node's time_sync_req)
|
|
||||||
2..5 current second (from epoch time 1970)
|
|
||||||
6 1/250ths fractions of current second
|
|
||||||
*/
|
|
||||||
|
|
||||||
// if no timesync handshake is pending then exit
|
// if no timesync handshake is pending then exit
|
||||||
if (!timeSyncPending)
|
if (!timeSyncPending)
|
||||||
return 0; // failure
|
return;
|
||||||
|
|
||||||
// extract 1 byte timerequest sequence number from payload
|
// mask application irq to ensure accurate timing
|
||||||
uint8_t seqNo = buf[0];
|
mask_user_IRQ();
|
||||||
buf++;
|
|
||||||
|
int rc = 0;
|
||||||
|
uint8_t seqNo = *(uint8_t *)pUserData;
|
||||||
|
uint16_t timestamp_msec;
|
||||||
|
uint32_t timestamp_sec;
|
||||||
|
|
||||||
|
#if (TIME_SYNC_LORASERVER)
|
||||||
|
|
||||||
|
// pUserData: contains pointer to payload buffer
|
||||||
|
// flag: length of buffer
|
||||||
|
|
||||||
|
// store LMIC time when we received the timesync answer
|
||||||
|
timesync_storeReq(osticks2ms(os_getTime()), timesync_rx);
|
||||||
|
|
||||||
|
// parse timesync_answer:
|
||||||
|
// byte meaning
|
||||||
|
// 0 sequence number (taken from node's time_sync_req)
|
||||||
|
// 1..4 current second (from UTC epoch)
|
||||||
|
// 5 1/250ths fractions of current second
|
||||||
|
|
||||||
|
// swap byte order from msb to lsb, note: this is a platform dependent hack
|
||||||
|
timestamp_sec = __builtin_bswap32(*(uint32_t *)(pUserData + 1));
|
||||||
|
|
||||||
|
// one step being 1/250th sec * 1000 = 4msec
|
||||||
|
timestamp_msec = *(uint8_t *)(pUserData + 5);
|
||||||
|
timestamp_msec *= 4;
|
||||||
|
|
||||||
// if no time is available or spurious buffer then exit
|
// if no time is available or spurious buffer then exit
|
||||||
if (buf_len != TIME_SYNC_FRAME_LENGTH) {
|
if (flag != TIME_SYNC_FRAME_LENGTH) {
|
||||||
if (seqNo == 0xff)
|
if (seqNo == TIMEREQUEST_END)
|
||||||
ESP_LOGI(TAG, "[%0.3f] Timeserver error: no confident time available",
|
ESP_LOGI(TAG, "[%0.3f] Timeserver error: no confident time available",
|
||||||
millis() / 1000.0);
|
millis() / 1000.0);
|
||||||
else
|
else
|
||||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: spurious data received",
|
ESP_LOGW(TAG, "[%0.3f] Timeserver error: spurious data received",
|
||||||
millis() / 1000.0);
|
millis() / 1000.0);
|
||||||
return 0; // failure
|
goto Exit; // failure
|
||||||
}
|
}
|
||||||
|
|
||||||
else { // we received a probably valid time frame
|
goto Finish;
|
||||||
|
|
||||||
// pointers to 4 bytes msb order
|
|
||||||
uint32_t timestamp_sec, *timestamp_ptr;
|
|
||||||
|
|
||||||
// extract 4 bytes containing gateway time in UTC seconds since unix
|
|
||||||
// epoch and convert it to uint32_t, octet order is big endian
|
|
||||||
timestamp_ptr = (uint32_t *)buf;
|
|
||||||
// swap byte order from msb to lsb, note: this is a platform dependent hack
|
|
||||||
timestamp_sec = __builtin_bswap32(*timestamp_ptr);
|
|
||||||
buf += 4;
|
|
||||||
|
|
||||||
// extract 1 byte containing fractional seconds in 2^-8 second steps
|
|
||||||
// one step being 1/250th sec * 1000 = 4msec
|
|
||||||
uint16_t timestamp_msec = buf[0] * 4;
|
|
||||||
// calculate absolute time received from gateway
|
|
||||||
time_t t = timestamp_sec + timestamp_msec / 1000;
|
|
||||||
|
|
||||||
// we guess timepoint is recent if it is newer than code compile date
|
|
||||||
if (timeIsValid(t)) {
|
|
||||||
ESP_LOGD(TAG, "[%0.3f] Timesync request seq#%d rcvd at %0.3f",
|
|
||||||
millis() / 1000.0, seqNo, osticks2ms(os_getTime()) / 1000.0);
|
|
||||||
|
|
||||||
// store time received from gateway
|
|
||||||
timesync_storeReq(timestamp_sec, gwtime_sec);
|
|
||||||
timesync_storeReq(timestamp_msec, gwtime_msec);
|
|
||||||
|
|
||||||
// inform processing task
|
|
||||||
xTaskNotify(timeSyncReqTask, seqNo, eSetBits);
|
|
||||||
|
|
||||||
return 1; // success
|
|
||||||
} else {
|
|
||||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: outdated time received",
|
|
||||||
millis() / 1000.0);
|
|
||||||
return 0; // failure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#elif (TIME_SYNC_LORAWAN)
|
#elif (TIME_SYNC_LORAWAN)
|
||||||
|
|
||||||
void IRAM_ATTR DevTimeAns_Cb(void *pUserData, int flagSuccess) {
|
// pUserData: contains pointer to SeqNo
|
||||||
// Explicit conversion from void* to uint8_t* to avoid compiler errors
|
// flag: indicates if we got a recent time from the network
|
||||||
uint8_t *seqNo = (uint8_t *)pUserData;
|
|
||||||
|
|
||||||
// mask application irq to ensure accurate timing
|
if (flag != 1) {
|
||||||
mask_user_IRQ();
|
ESP_LOGW(TAG, "[%0.3f] Network did not answer time request",
|
||||||
|
millis() / 1000.0);
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
// A struct that will be populated by LMIC_getNetworkTimeReference.
|
// A struct that will be populated by LMIC_getNetworkTimeReference.
|
||||||
// It contains the following fields:
|
// It contains the following fields:
|
||||||
@ -261,39 +244,38 @@ void IRAM_ATTR DevTimeAns_Cb(void *pUserData, int flagSuccess) {
|
|||||||
// the gateway received the time request
|
// the gateway received the time request
|
||||||
lmic_time_reference_t lmicTime;
|
lmic_time_reference_t lmicTime;
|
||||||
|
|
||||||
if (flagSuccess != 1) {
|
|
||||||
ESP_LOGW(TAG, "Network did not answer time request");
|
|
||||||
goto Finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time_sync_seqNo != *seqNo) {
|
|
||||||
ESP_LOGW(TAG, "Network timesync handshake failed, seqNo#%u, *seqNo");
|
|
||||||
goto Finish;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate lmic_time_reference
|
// Populate lmic_time_reference
|
||||||
if ((LMIC_getNetworkTimeReference(&lmicTime)) != 1) {
|
if ((LMIC_getNetworkTimeReference(&lmicTime)) != 1) {
|
||||||
ESP_LOGW(TAG, "Network time request failed");
|
ESP_LOGW(TAG, "[%0.3f] Network time request failed", millis() / 1000.0);
|
||||||
|
goto Exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate UTCTime, considering the difference between GPS and UTC time
|
||||||
|
timestamp_sec = lmicTime.tNetwork + GPS_UTC_DIFF;
|
||||||
|
// Add delay between the instant the time was transmitted and the current time
|
||||||
|
timestamp_msec = osticks2ms(os_getTime() - lmicTime.tLocal);
|
||||||
goto Finish;
|
goto Finish;
|
||||||
|
|
||||||
|
#endif // (TIME_SYNC_LORAWAN)
|
||||||
|
|
||||||
|
Finish:
|
||||||
|
// check if calucalted time is recent
|
||||||
|
if (timeIsValid(timestamp_sec)) {
|
||||||
|
// store time received from gateway
|
||||||
|
timesync_storeReq(timestamp_sec, gwtime_sec);
|
||||||
|
timesync_storeReq(timestamp_msec, gwtime_msec);
|
||||||
|
// success
|
||||||
|
rc = 1;
|
||||||
|
} else {
|
||||||
|
ESP_LOGW(TAG, "[%0.3f] Timeserver error: outdated time received",
|
||||||
|
millis() / 1000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Exit:
|
||||||
|
// end of time critical section: release app irq lock
|
||||||
|
unmask_user_IRQ();
|
||||||
|
// inform processing task
|
||||||
|
xTaskNotify(timeSyncProcTask, rc ? seqNo : TIMEREQUEST_END, eSetBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate UTCTime, considering the difference between GPS and UTC time
|
|
||||||
timestamp_sec = lmicTime.tNetwork + GPS_UTC_DIFF;
|
|
||||||
// Add delay between the instant the time was transmitted and the current time
|
|
||||||
timestamp_msec = osticks2ms(os_getTime() - lmicTime.tLocal);
|
|
||||||
|
|
||||||
// store time received from gateway
|
|
||||||
timesync_storeReq(timestamp_sec, gwtime_sec);
|
|
||||||
timesync_storeReq(timestamp_msec, gwtime_msec);
|
|
||||||
|
|
||||||
// inform processing task
|
|
||||||
xTaskNotify(timeSyncReqTask, *seqNo, eSetBits);
|
|
||||||
|
|
||||||
Finish :
|
|
||||||
// end of time critical section: release app irq lock
|
|
||||||
unmask_user_IRQ();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // HAS_LORA
|
#endif // HAS_LORA
|
Loading…
Reference in New Issue
Block a user