diff --git a/include/timesync.h b/include/timesync.h index c102c1c4..8b8c42bc 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -7,13 +7,15 @@ #include "timekeeper.h" //#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds] -#define TIME_SYNC_FRAME_LENGTH 0x05 // timeserver answer frame length [bytes] +#define TIME_SYNC_FRAME_LENGTH 0x07 // timeserver answer frame length [bytes] #define TIME_SYNC_FIXUP 4 // calibration to fixup processing time [milliseconds] void timesync_init(void); void send_timesync_req(void); -int recv_timesync_ans(const uint8_t seq_no, const uint8_t buf[], const uint8_t buf_len); + +int recv_timesync_ans(const uint8_t buf[], uint8_t buf_len); + void process_timesync_req(void *taskparameter); void store_time_sync_req(uint32_t t_millisec); -#endif \ No newline at end of file +#endif diff --git a/src/Timeserver/Nodered-Timeserver.json b/src/Timeserver/Nodered-Timeserver.json index 85e52607..3319b39d 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];\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];", + "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[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(7);\nnew DataView(buf).setUint8(0, seqNo);\n// Timezone (in 15min steps)\nvar timezone = 8; // CET = UTC+2h\nnew 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;\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];", "outputs": 5, "noerr": 0, "x": 350, @@ -384,4 +384,4 @@ "disabled": false, "hidden": false } -] \ No newline at end of file +] diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 088d4305..e6aa7114 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -561,8 +561,8 @@ void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, #if (TIME_SYNC_LORASERVER) // valid timesync answer -> call timesync processor - if ((port >= TIMEANSWERPORT_MIN) && (port <= TIMEANSWERPORT_MAX)) { - recv_timesync_ans(port, pMsg, nMsg); + if (port == TIMEPORT) { + recv_timesync_ans(pMsg, nMsg); break; } #endif diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 40852f59..f7826a9a 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -30,7 +30,7 @@ * |< Scan Window > |< Scan Window > | ... |< Scan Window > | * |< Scan Interval >|< Scan Interval >| ... |< Scan Interval >| * |< Scan duration >| -* +* * Scan duration sets how long scanning should be going on, before starting a new scan cycle. 0 means infinite (default). * Scan window sets how much of the interval should be occupied by scanning. Should be >= BLESCANINTERVAL. * Scan interval is how long scanning should be done on each channel. BLE uses 3 channels for advertising. @@ -97,9 +97,9 @@ #define BEACONPORT 6 // beacon alarms #define BMEPORT 7 // BME680 sensor #define BATTPORT 8 // battery voltage -#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 TIMEPORT 9 // time query and response +#define TIMEDIFFPORT 13 // time adjust diff +#define TIMEREQUEST_MAX_SEQNO 250 // time answer, start 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 8ce3238f..a50d7ca1 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -25,7 +25,7 @@ typedef std::chrono::duration> TaskHandle_t timeSyncReqTask = NULL; -static uint8_t time_sync_seqNo = random(TIMEANSWERPORT_MIN, TIMEANSWERPORT_MAX); +static uint8_t time_sync_seqNo = 0; static bool timeSyncPending = false; static myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES]; static myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES]; @@ -93,9 +93,10 @@ void process_timesync_req(void *taskparameter) { 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; + time_sync_seqNo++; + if(time_sync_seqNo > TIMEREQUEST_MAX_SEQNO) { + time_sync_seqNo = 0; + } if (i < TIME_SYNC_SAMPLES - 1) { // wait until next cycle @@ -154,7 +155,9 @@ void store_time_sync_req(uint32_t timestamp) { } // process timeserver timestamp answer, called from lorawan.cpp -int recv_timesync_ans(const uint8_t seq_no, const uint8_t buf[], const uint8_t buf_len) { +int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) { + uint8_t seq_no = buf[0]; + buf++; // if no timesync handshake is pending then exit if (!timeSyncPending) @@ -177,9 +180,14 @@ int recv_timesync_ans(const uint8_t seq_no, const uint8_t buf[], const uint8_t b // the 5th byte contains the fractional seconds in 2^-8 second steps // (= 1/250th sec), we convert this to ms - uint16_t timestamp_msec = 4 * buf[4]; - // pointers to 4 bytes containing UTC seconds since unix epoch, msb + uint16_t timestamp_msec = 4 * buf[6]; + // pointers to 4 bytes 4 bytes containing UTC seconds since unix epoch, msb uint32_t timestamp_sec, *timestamp_ptr; + uint32_t timezone_sec; + + // extract timezone from buffer (in 15min steps, one step being 15min * 60s = 900s) + timezone_sec = buf[0]*900; + buf++; // convert buffer to uint32_t, octet order is big endian timestamp_ptr = (uint32_t *)buf; @@ -187,7 +195,7 @@ int recv_timesync_ans(const uint8_t seq_no, const uint8_t buf[], const uint8_t b timestamp_sec = __builtin_bswap32(*timestamp_ptr); // construct the timepoint when message was seen on gateway - time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec); + time_sync_rx[k] += seconds(timestamp_sec+timezone_sec) + milliseconds(timestamp_msec); // we guess timepoint is recent if it newer than code compile date if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) { @@ -218,4 +226,4 @@ void timesync_init() { 1); // CPU core } -#endif \ No newline at end of file +#endif