Merge pull request #566 from cyberman54/development
Improvements Timeserver & MAC command printout
This commit is contained in:
commit
0d26f629d0
@ -20,13 +20,6 @@
|
|||||||
|
|
||||||
extern TaskHandle_t lmicTask, lorasendTask;
|
extern TaskHandle_t lmicTask, lorasendTask;
|
||||||
|
|
||||||
// table of LORAWAN MAC commands
|
|
||||||
typedef struct {
|
|
||||||
const uint8_t opcode;
|
|
||||||
const char cmdname[20];
|
|
||||||
const uint8_t params;
|
|
||||||
} mac_t;
|
|
||||||
|
|
||||||
esp_err_t lora_stack_init(bool do_join);
|
esp_err_t lora_stack_init(bool do_join);
|
||||||
void lora_setupForNetwork(bool preJoin);
|
void lora_setupForNetwork(bool preJoin);
|
||||||
void lmictask(void *pvParameters);
|
void lmictask(void *pvParameters);
|
||||||
@ -36,19 +29,56 @@ void get_hard_deveui(uint8_t *pdeveui);
|
|||||||
void os_getDevKey(u1_t *buf);
|
void os_getDevKey(u1_t *buf);
|
||||||
void os_getArtEui(u1_t *buf);
|
void os_getArtEui(u1_t *buf);
|
||||||
void os_getDevEui(u1_t *buf);
|
void os_getDevEui(u1_t *buf);
|
||||||
void showLoraKeys(void);
|
|
||||||
void lora_send(void *pvParameters);
|
void lora_send(void *pvParameters);
|
||||||
void lora_enqueuedata(MessageBuffer_t *message);
|
void lora_enqueuedata(MessageBuffer_t *message);
|
||||||
void lora_queuereset(void);
|
void lora_queuereset(void);
|
||||||
void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev);
|
void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev);
|
||||||
void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port,
|
void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
|
||||||
const uint8_t *pMsg, size_t nMsg);
|
size_t nMsg);
|
||||||
//void IRAM_ATTR myTxCallback(void *pUserData, int fSuccess);
|
void IRAM_ATTR myTxCallback(void *pUserData, int fSuccess);
|
||||||
void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, const mac_t table[],
|
|
||||||
const uint8_t tablesize);
|
|
||||||
//u1_t os_getBattLevel(void);
|
|
||||||
const char *getSfName(rps_t rps);
|
const char *getSfName(rps_t rps);
|
||||||
const char *getBwName(rps_t rps);
|
const char *getBwName(rps_t rps);
|
||||||
const char *getCrName(rps_t rps);
|
const char *getCrName(rps_t rps);
|
||||||
|
// u1_t os_getBattLevel(void);
|
||||||
|
|
||||||
|
#if (VERBOSE)
|
||||||
|
|
||||||
|
// a table for storage of LORAWAN MAC commands
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t cid;
|
||||||
|
const char cmdname[20];
|
||||||
|
const uint8_t params;
|
||||||
|
} mac_t;
|
||||||
|
|
||||||
|
// table of LORAWAN MAC messages sent by the network to the device
|
||||||
|
// format: CDI, Command (max 19 chars), #parameters (bytes)
|
||||||
|
// source: LoRaWAN 1.1 Specification (October 11, 2017)
|
||||||
|
static const mac_t MACdn_table[] = {
|
||||||
|
{0x01, "ResetConf", 1}, {0x02, "LinkCheckAns", 2},
|
||||||
|
{0x03, "LinkADRReq", 4}, {0x04, "DutyCycleReq", 1},
|
||||||
|
{0x05, "RXParamSetupReq", 4}, {0x06, "DevStatusReq", 0},
|
||||||
|
{0x07, "NewChannelReq", 5}, {0x08, "RxTimingSetupReq", 1},
|
||||||
|
{0x09, "TxParamSetupReq", 1}, {0x0A, "DlChannelReq", 4},
|
||||||
|
{0x0B, "RekeyConf", 1}, {0x0C, "ADRParamSetupReq", 1},
|
||||||
|
{0x0D, "DeviceTimeAns", 5}, {0x0E, "ForceRejoinReq", 2},
|
||||||
|
{0x0F, "RejoinParamSetupReq", 1}};
|
||||||
|
|
||||||
|
static const uint8_t MACdn_tSize = sizeof(MACdn_table) / sizeof(MACdn_table[0]);
|
||||||
|
|
||||||
|
// table of LORAWAN MAC messages sent by the device to the network
|
||||||
|
static const mac_t MACup_table[] = {
|
||||||
|
{0x01, "ResetInd", 1}, {0x02, "LinkCheckReq", 0},
|
||||||
|
{0x03, "LinkADRAns", 1}, {0x04, "DutyCycleAns", 0},
|
||||||
|
{0x05, "RXParamSetupAns", 1}, {0x06, "DevStatusAns", 2},
|
||||||
|
{0x07, "NewChannelAns", 1}, {0x08, "RxTimingSetupAns", 0},
|
||||||
|
{0x09, "TxParamSetupAns", 0}, {0x0A, "DlChannelAns", 1},
|
||||||
|
{0x0B, "RekeyInd", 1}, {0x0C, "ADRParamSetupAns", 0},
|
||||||
|
{0x0D, "DeviceTimeReq", 0}, {0x0F, "RejoinParamSetupAns", 1}};
|
||||||
|
|
||||||
|
static const uint8_t MACup_tSize = sizeof(MACup_table) / sizeof(MACup_table[0]);
|
||||||
|
|
||||||
|
void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, bool is_down);
|
||||||
|
void showLoraKeys(void);
|
||||||
|
#endif // VERBOSE
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -17,15 +17,18 @@ enum timesync_t {
|
|||||||
timesync_rx,
|
timesync_rx,
|
||||||
gwtime_sec,
|
gwtime_sec,
|
||||||
gwtime_msec,
|
gwtime_msec,
|
||||||
gwtime_tzsec,
|
|
||||||
no_of_timestamps
|
no_of_timestamps
|
||||||
};
|
};
|
||||||
|
|
||||||
void timesync_init(void);
|
void timesync_init(void);
|
||||||
void send_timesync_req(void);
|
void timesync_sendReq(void);
|
||||||
int recv_timesync_ans(const uint8_t buf[], uint8_t buf_len);
|
void timesync_storeReq(uint32_t timestamp, timesync_t timestamp_type);
|
||||||
void store_timestamp(uint32_t timestamp, timesync_t timestamp_type);
|
void IRAM_ATTR timesync_processReq(void *taskparameter);
|
||||||
void IRAM_ATTR process_timesync_req(void *taskparameter);
|
|
||||||
void IRAM_ATTR process_timesync_req(void *pVoidUserUTCTime, int flagSuccess);
|
#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.93
|
release_version = 1.9.94
|
||||||
; 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,61 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"id": "9b4f492d.fbfd18",
|
|
||||||
"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": 220,
|
|
||||||
"y": 520,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"53a85e2c.2728d"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "9c105726.613a58",
|
"id": "9c105726.613a58",
|
||||||
"type": "mqtt in",
|
"type": "mqtt in",
|
||||||
@ -72,19 +15,6 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "1c9a7438.6e38ec",
|
|
||||||
"type": "mqtt out",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "send",
|
|
||||||
"topic": "",
|
|
||||||
"qos": "",
|
|
||||||
"retain": "",
|
|
||||||
"broker": "2a15ab6f.ab2244",
|
|
||||||
"x": 710,
|
|
||||||
"y": 520,
|
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "113ef524.57edeb",
|
"id": "113ef524.57edeb",
|
||||||
"type": "json",
|
"type": "json",
|
||||||
@ -126,22 +56,6 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "90e76b02.6298f8",
|
|
||||||
"type": "json",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "Convert",
|
|
||||||
"property": "payload",
|
|
||||||
"action": "",
|
|
||||||
"pretty": false,
|
|
||||||
"x": 560,
|
|
||||||
"y": 520,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"1c9a7438.6e38ec"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "d6f27e8e.93242",
|
"id": "d6f27e8e.93242",
|
||||||
"type": "base64",
|
"type": "base64",
|
||||||
@ -158,28 +72,13 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "53a85e2c.2728d",
|
|
||||||
"type": "base64",
|
|
||||||
"z": "449c1517.e25f4c",
|
|
||||||
"name": "Encode",
|
|
||||||
"action": "",
|
|
||||||
"property": "payload.payload_raw",
|
|
||||||
"x": 400,
|
|
||||||
"y": 520,
|
|
||||||
"wires": [
|
|
||||||
[
|
|
||||||
"90e76b02.6298f8"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "15980d22.6f4663",
|
"id": "15980d22.6f4663",
|
||||||
"type": "comment",
|
"type": "comment",
|
||||||
"z": "449c1517.e25f4c",
|
"z": "449c1517.e25f4c",
|
||||||
"name": "LoRaWAN Timeserver v1.21",
|
"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.",
|
"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,
|
"x": 150,
|
||||||
"y": 47,
|
"y": 47,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
@ -188,7 +87,7 @@
|
|||||||
"type": "function",
|
"type": "function",
|
||||||
"z": "449c1517.e25f4c",
|
"z": "449c1517.e25f4c",
|
||||||
"name": "Timeserver Logic",
|
"name": "Timeserver Logic",
|
||||||
"func": "/* LoRaWAN Timeserver\n\nconstruct 7 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 timezone in 15 minutes steps\n3..6 current second (from epoch time 1970)\n7 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\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(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; // 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);\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];",
|
||||||
"outputs": 5,
|
"outputs": 5,
|
||||||
"noerr": 0,
|
"noerr": 0,
|
||||||
"x": 330,
|
"x": 330,
|
||||||
@ -251,32 +150,6 @@
|
|||||||
"y": 307,
|
"y": 307,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "de908e66.b6fd3",
|
|
||||||
"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": 690,
|
|
||||||
"y": 387,
|
|
||||||
"wires": []
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "6aeb3720.a89618",
|
"id": "6aeb3720.a89618",
|
||||||
"type": "ui_text",
|
"type": "ui_text",
|
||||||
@ -309,6 +182,147 @@
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"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",
|
||||||
|
"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": 220,
|
||||||
|
"y": 520,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"53a85e2c.2728d"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1c9a7438.6e38ec",
|
||||||
|
"type": "mqtt out",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "send",
|
||||||
|
"topic": "",
|
||||||
|
"qos": "",
|
||||||
|
"retain": "",
|
||||||
|
"broker": "2a15ab6f.ab2244",
|
||||||
|
"x": 710,
|
||||||
|
"y": 520,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "90e76b02.6298f8",
|
||||||
|
"type": "json",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "Convert",
|
||||||
|
"property": "payload",
|
||||||
|
"action": "",
|
||||||
|
"pretty": false,
|
||||||
|
"x": 560,
|
||||||
|
"y": 520,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"1c9a7438.6e38ec"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "53a85e2c.2728d",
|
||||||
|
"type": "base64",
|
||||||
|
"z": "449c1517.e25f4c",
|
||||||
|
"name": "Encode",
|
||||||
|
"action": "",
|
||||||
|
"property": "payload.payload_raw",
|
||||||
|
"x": 400,
|
||||||
|
"y": 520,
|
||||||
|
"wires": [
|
||||||
|
[
|
||||||
|
"90e76b02.6298f8"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "de908e66.b6fd3",
|
||||||
|
"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": 690,
|
||||||
|
"y": 387,
|
||||||
|
"wires": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "d5a35bab.44cb18",
|
"id": "d5a35bab.44cb18",
|
||||||
"type": "ui_text",
|
"type": "ui_text",
|
||||||
@ -341,20 +355,6 @@
|
|||||||
"y": 467,
|
"y": 467,
|
||||||
"wires": []
|
"wires": []
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"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": "2a15ab6f.ab2244",
|
"id": "2a15ab6f.ab2244",
|
||||||
"type": "mqtt-broker",
|
"type": "mqtt-broker",
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
/* LoRaWAN Timeserver
|
/* LoRaWAN Timeserver
|
||||||
|
|
||||||
construct 7 byte timesync_answer from gateway timestamp and node's time_sync_req
|
VERSION: 1.3
|
||||||
|
|
||||||
|
construct 6 byte timesync_answer from gateway timestamp and node's time_sync_req
|
||||||
|
|
||||||
byte meaning
|
byte meaning
|
||||||
1 sequence number (taken from node's time_sync_req)
|
1 sequence number (taken from node's time_sync_req)
|
||||||
2 timezone in 15 minutes steps
|
2..5 current second (from GPS epoch starting 1980)
|
||||||
3..6 current second (from epoch time 1970)
|
6 1/250ths fractions of current second
|
||||||
7 1/250ths fractions of current second
|
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ function timecompare(a, b) {
|
|||||||
return comparison;
|
return comparison;
|
||||||
}
|
}
|
||||||
|
|
||||||
let confidence = 2000; // max millisecond diff gateway time to server time
|
let confidence = 1000; // max millisecond diff gateway time to server time
|
||||||
|
|
||||||
// guess if we have received a valid time_sync_req command
|
// guess if we have received a valid time_sync_req command
|
||||||
if (msg.payload.payload_raw.length != 1)
|
if (msg.payload.payload_raw.length != 1)
|
||||||
@ -72,11 +73,11 @@ var offset = server_time - timestamp;
|
|||||||
var seconds = Math.floor(timestamp/1000);
|
var seconds = Math.floor(timestamp/1000);
|
||||||
var fractions = (timestamp % 1000) / 4;
|
var fractions = (timestamp % 1000) / 4;
|
||||||
|
|
||||||
let buf = new ArrayBuffer(7);
|
let buf = new ArrayBuffer(6);
|
||||||
new DataView(buf).setUint8(0, seqNo);
|
new DataView(buf).setUint8(0, seqNo);
|
||||||
// Timezone (in 15min steps)
|
// Timezone (in 15min steps) -> deprecated
|
||||||
var timezone = 8; // CET = UTC+2h
|
//var timezone = 8; // CET = UTC+2h
|
||||||
new DataView(buf).setUint8(1, timezone);
|
//new DataView(buf).setUint8(1, timezone);
|
||||||
new DataView(buf).setUint32(2, seconds);
|
new DataView(buf).setUint32(2, seconds);
|
||||||
new DataView(buf).setUint8(6, fractions);
|
new DataView(buf).setUint8(6, fractions);
|
||||||
|
|
||||||
|
@ -378,9 +378,10 @@ start:
|
|||||||
LMIC.datarate);
|
LMIC.datarate);
|
||||||
dp_printf(0, 5, FONT_SMALL, 0, "ChMsk:%04X Nonce:%04X", LMIC.channelMap,
|
dp_printf(0, 5, FONT_SMALL, 0, "ChMsk:%04X Nonce:%04X", LMIC.channelMap,
|
||||||
LMIC.devNonce);
|
LMIC.devNonce);
|
||||||
dp_printf(0, 6, FONT_SMALL, 0, "CUp:%-6d CDn:%-6d", LMIC.seqnoUp,
|
dp_printf(0, 6, FONT_SMALL, 0, "fUp:%-6d fDn:%-6d",
|
||||||
LMIC.seqnoDn);
|
LMIC.seqnoUp ? LMIC.seqnoUp - 1 : 0,
|
||||||
dp_printf(0, 7, FONT_SMALL, 0, "SNR:%-5d RSSI:%-5d", LMIC.snr / 4,
|
LMIC.seqnoDn ? LMIC.seqnoDn - 1 : 0);
|
||||||
|
dp_printf(0, 7, FONT_SMALL, 0, "SNR:%-5d RSSI:%-5d", (LMIC.snr + 2) / 4,
|
||||||
LMIC.rssi);
|
LMIC.rssi);
|
||||||
break; // page5
|
break; // page5
|
||||||
#else // don't show blank page if we are unattended
|
#else // don't show blank page if we are unattended
|
||||||
|
@ -23,9 +23,6 @@
|
|||||||
// time sync via LoRaWAN network, note: not supported by TTNv2
|
// time sync via LoRaWAN network, note: not supported by TTNv2
|
||||||
#define LMIC_ENABLE_DeviceTimeReq 1
|
#define LMIC_ENABLE_DeviceTimeReq 1
|
||||||
|
|
||||||
// use callback event handlers, not onEvent() reference
|
|
||||||
#define LMIC_ENABLE_onEvent 0
|
|
||||||
|
|
||||||
// This tells LMIC to make the receive windows bigger, in case your clock is
|
// This tells LMIC to make the receive windows bigger, in case your clock is
|
||||||
// faster or slower. This causes the transceiver to be earlier switched on,
|
// faster or slower. This causes the transceiver to be earlier switched on,
|
||||||
// so consuming more power. You may sharpen (reduce) this value if you are
|
// so consuming more power. You may sharpen (reduce) this value if you are
|
||||||
|
196
src/lorawan.cpp
196
src/lorawan.cpp
@ -23,29 +23,6 @@ RTC_NOINIT_ATTR int RTCseqnoUp, RTCseqnoDn;
|
|||||||
QueueHandle_t LoraSendQueue;
|
QueueHandle_t LoraSendQueue;
|
||||||
TaskHandle_t lmicTask = NULL, lorasendTask = NULL;
|
TaskHandle_t lmicTask = NULL, lorasendTask = NULL;
|
||||||
|
|
||||||
// table of LORAWAN MAC messages sent by the network to the device
|
|
||||||
// format: opcode, cmdname (max 19 chars), #bytes params
|
|
||||||
// source: LoRaWAN 1.1 Specification (October 11, 2017)
|
|
||||||
static const mac_t MACdn_table[] = {
|
|
||||||
{0x01, "ResetConf", 1}, {0x02, "LinkCheckAns", 2},
|
|
||||||
{0x03, "LinkADRReq", 4}, {0x04, "DutyCycleReq", 1},
|
|
||||||
{0x05, "RXParamSetupReq", 4}, {0x06, "DevStatusReq", 0},
|
|
||||||
{0x07, "NewChannelReq", 5}, {0x08, "RxTimingSetupReq", 1},
|
|
||||||
{0x09, "TxParamSetupReq", 1}, {0x0A, "DlChannelReq", 4},
|
|
||||||
{0x0B, "RekeyConf", 1}, {0x0C, "ADRParamSetupReq", 1},
|
|
||||||
{0x0D, "DeviceTimeAns", 5}, {0x0E, "ForceRejoinReq", 2},
|
|
||||||
{0x0F, "RejoinParamSetupReq", 1}};
|
|
||||||
|
|
||||||
// table of LORAWAN MAC messages sent by the device to the network
|
|
||||||
static const mac_t MACup_table[] = {
|
|
||||||
{0x01, "ResetInd", 1}, {0x02, "LinkCheckReq", 0},
|
|
||||||
{0x03, "LinkADRAns", 1}, {0x04, "DutyCycleAns", 0},
|
|
||||||
{0x05, "RXParamSetupAns", 1}, {0x06, "DevStatusAns", 2},
|
|
||||||
{0x07, "NewChannelAns", 1}, {0x08, "RxTimingSetupAns", 0},
|
|
||||||
{0x09, "TxParamSetupAns", 0}, {0x0A, "DlChannelAns", 1},
|
|
||||||
{0x0B, "RekeyInd", 1}, {0x0C, "ADRParamSetupAns", 0},
|
|
||||||
{0x0D, "DeviceTimeReq", 0}, {0x0F, "RejoinParamSetupAns", 1}};
|
|
||||||
|
|
||||||
class MyHalConfig_t : public Arduino_LMIC::HalConfiguration_t {
|
class MyHalConfig_t : public Arduino_LMIC::HalConfiguration_t {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -100,7 +77,7 @@ void lora_setupForNetwork(bool preJoin) {
|
|||||||
// show current devaddr
|
// show current devaddr
|
||||||
ESP_LOGI(TAG, "DEVaddr: 0x%08X | Network ID: 0x%06X | Network Type: %d",
|
ESP_LOGI(TAG, "DEVaddr: 0x%08X | Network ID: 0x%06X | Network Type: %d",
|
||||||
LMIC.devaddr, LMIC.netid & 0x001FFFFF, LMIC.netid & 0x00E00000);
|
LMIC.devaddr, LMIC.netid & 0x001FFFFF, LMIC.netid & 0x00E00000);
|
||||||
ESP_LOGI(TAG, "RSSI: %d | SNR: %d", LMIC.rssi, LMIC.snr / 4);
|
ESP_LOGI(TAG, "RSSI: %d | SNR: %d", LMIC.rssi, (LMIC.snr + 2) / 4);
|
||||||
ESP_LOGI(TAG, "Radio parameters: %s | %s | %s",
|
ESP_LOGI(TAG, "Radio parameters: %s | %s | %s",
|
||||||
getSfName(updr2rps(LMIC.datarate)),
|
getSfName(updr2rps(LMIC.datarate)),
|
||||||
getBwName(updr2rps(LMIC.datarate)),
|
getBwName(updr2rps(LMIC.datarate)),
|
||||||
@ -265,18 +242,16 @@ void lora_send(void *pvParameters) {
|
|||||||
SendBuffer.MessageSize,
|
SendBuffer.MessageSize,
|
||||||
(cfg.countermode & 0x02))) {
|
(cfg.countermode & 0x02))) {
|
||||||
|
|
||||||
// switch (LMIC_sendWithCallback_strict(
|
|
||||||
// SendBuffer.MessagePort, SendBuffer.Message,
|
|
||||||
// SendBuffer.MessageSize, (cfg.countermode & 0x02), myTxCallback,
|
|
||||||
// &SendBuffer.MessagePort)) {
|
|
||||||
|
|
||||||
case LMIC_ERROR_SUCCESS:
|
case LMIC_ERROR_SUCCESS:
|
||||||
|
// save current Fcnt to RTC RAM
|
||||||
|
RTCseqnoUp = LMIC.seqnoUp;
|
||||||
|
RTCseqnoDn = LMIC.seqnoDn;
|
||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER)
|
||||||
// if last packet sent was a timesync request, store TX timestamp
|
// if last packet sent was a timesync request, store TX timestamp
|
||||||
if (SendBuffer.MessagePort == TIMEPORT)
|
if (SendBuffer.MessagePort == TIMEPORT)
|
||||||
// store LMIC time when we started transmit of timesync request
|
// store LMIC time when we started transmit of timesync request
|
||||||
store_timestamp(osticks2ms(os_getTime()), timesync_tx);
|
timesync_storeReq(osticks2ms(os_getTime()), timesync_tx);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize);
|
ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize);
|
||||||
@ -429,6 +404,15 @@ void myEventCallback(void *pUserData, ev_t ev) {
|
|||||||
|
|
||||||
// process current event message
|
// process current event message
|
||||||
switch (ev) {
|
switch (ev) {
|
||||||
|
|
||||||
|
case EV_TXCOMPLETE:
|
||||||
|
// -> processed in lora_send()
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EV_RXCOMPLETE:
|
||||||
|
// -> processed in myRxCallback()
|
||||||
|
break;
|
||||||
|
|
||||||
case EV_JOINING:
|
case EV_JOINING:
|
||||||
// do the network-specific setup prior to join.
|
// do the network-specific setup prior to join.
|
||||||
lora_setupForNetwork(true);
|
lora_setupForNetwork(true);
|
||||||
@ -439,12 +423,6 @@ void myEventCallback(void *pUserData, ev_t ev) {
|
|||||||
lora_setupForNetwork(false);
|
lora_setupForNetwork(false);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EV_TXCOMPLETE:
|
|
||||||
// save current Fcnt to RTC RAM
|
|
||||||
RTCseqnoUp = LMIC.seqnoUp;
|
|
||||||
RTCseqnoDn = LMIC.seqnoDn;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EV_JOIN_TXCOMPLETE:
|
case EV_JOIN_TXCOMPLETE:
|
||||||
// replace descriptor from library with more descriptive term
|
// replace descriptor from library with more descriptive term
|
||||||
snprintf(lmic_event_msg, LMIC_EVENTMSG_LEN, "%-16s", "JOIN_WAIT");
|
snprintf(lmic_event_msg, LMIC_EVENTMSG_LEN, "%-16s", "JOIN_WAIT");
|
||||||
@ -462,113 +440,71 @@ void myEventCallback(void *pUserData, ev_t ev) {
|
|||||||
ESP_LOGD(TAG, "%s", lmic_event_msg);
|
ESP_LOGD(TAG, "%s", lmic_event_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// receive message handler
|
// event EV_RXCOMPLETE message handler
|
||||||
void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
|
void myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
|
||||||
size_t nMsg) {
|
size_t nMsg) {
|
||||||
|
|
||||||
// display type of received data
|
// display amount of received data
|
||||||
if (nMsg)
|
if (nMsg)
|
||||||
ESP_LOGI(TAG, "Received %u byte(s) of payload on port %u", nMsg, port);
|
ESP_LOGI(TAG, "Received %u byte(s) of payload on port %u", nMsg, port);
|
||||||
else if (port)
|
else if (port)
|
||||||
ESP_LOGI(TAG, "Received empty message on port %u", port);
|
ESP_LOGI(TAG, "Received empty message on port %u", port);
|
||||||
|
|
||||||
// list MAC messages, if any
|
|
||||||
uint8_t nMac = pMsg - &LMIC.frame[0];
|
|
||||||
if (port != MACPORT)
|
|
||||||
--nMac;
|
|
||||||
if (nMac) {
|
|
||||||
ESP_LOGI(TAG, "%u byte(s) downlink MAC commands", nMac);
|
|
||||||
// NOT WORKING YET
|
|
||||||
// whe need to unwrap the MAC command from LMIC.frame here
|
|
||||||
// mac_decode(LMIC.frame, nMac, MACdn_table, sizeof(MACdn_table) /
|
|
||||||
// sizeof(MACdn_table[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LMIC.pendMacLen) {
|
|
||||||
ESP_LOGI(TAG, "%u byte(s) uplink MAC commands", LMIC.pendMacLen);
|
|
||||||
mac_decode(LMIC.pendMacData, LMIC.pendMacLen, MACup_table,
|
|
||||||
sizeof(MACup_table) / sizeof(MACup_table[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (port) {
|
switch (port) {
|
||||||
|
|
||||||
// ignore mac messages
|
// decode mac messages if we want to print those
|
||||||
|
#if (VERBOSE)
|
||||||
case MACPORT:
|
case MACPORT:
|
||||||
break;
|
// decode downlink MAC commands
|
||||||
|
if (LMIC.dataBeg)
|
||||||
|
mac_decode(LMIC.frame, LMIC.dataBeg, true);
|
||||||
|
// decode uplink MAC commands
|
||||||
|
if (LMIC.pendMacLen)
|
||||||
|
mac_decode(LMIC.pendMacData, LMIC.pendMacLen, false);
|
||||||
|
break; // do not fallthrough to default, we are done
|
||||||
|
#endif
|
||||||
|
|
||||||
// rcommand received -> call interpreter
|
// rcommand received -> call interpreter
|
||||||
case RCMDPORT:
|
case RCMDPORT:
|
||||||
rcommand(pMsg, nMsg);
|
rcommand(pMsg, nMsg);
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
|
|
||||||
|
// timeserver answer -> call timesync processor
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER)
|
||||||
// valid timesync answer -> call timesync processor
|
case TIMEPORT:
|
||||||
if (port == TIMEPORT) {
|
|
||||||
// store LMIC time when we received the timesync answer
|
// store LMIC time when we received the timesync answer
|
||||||
store_timestamp(osticks2ms(os_getTime()), timesync_rx);
|
timesync_storeReq(osticks2ms(os_getTime()), timesync_rx);
|
||||||
// get and store gwtime from payload
|
// get and store gwtime from payload
|
||||||
recv_timesync_ans(pMsg, nMsg);
|
recv_timeserver_ans(pMsg, nMsg);
|
||||||
break;
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// unknown port -> display info
|
// decode any piggybacked downlink MAC commands if we want to print those
|
||||||
ESP_LOGI(TAG, "Received data on unsupported port %u", port);
|
default:
|
||||||
|
#if (VERBOSE)
|
||||||
|
if (LMIC.dataBeg > 1)
|
||||||
|
mac_decode(LMIC.frame, LMIC.dataBeg - 1, true);
|
||||||
|
#endif // VERBOSE
|
||||||
|
|
||||||
break;
|
break;
|
||||||
} // switch
|
} // switch
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
// event TRANSMIT COMPLETE message handler
|
// event EV_TXCOMPLETE message handler
|
||||||
void myTxCallback(void *pUserData, int fSuccess) {
|
void myTxCallback(void *pUserData, int fSuccess) {
|
||||||
|
|
||||||
uint8_t *const sendport = (uint8_t *)pUserData;
|
uint8_t *const pMsg = (uint8_t *)pUserData;
|
||||||
|
|
||||||
|
// LMIC did successful transmit data
|
||||||
if (fSuccess) {
|
if (fSuccess) {
|
||||||
// LMIC did tx on *sendport -> nothing yet to do here
|
RTCseqnoUp = LMIC.seqnoUp;
|
||||||
|
RTCseqnoDn = LMIC.seqnoDn;
|
||||||
} else {
|
} else {
|
||||||
// LMIC could not tx on *sendport -> error handling yet to come
|
// LMIC could not transmit data
|
||||||
|
// -> error handling yet to come
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// decode LORAWAN MAC message
|
|
||||||
void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, const mac_t table[],
|
|
||||||
const uint8_t tablesize) {
|
|
||||||
|
|
||||||
if (!cmdlen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
uint8_t foundcmd[cmdlen], cursor = 0;
|
|
||||||
|
|
||||||
while (cursor < cmdlen) {
|
|
||||||
|
|
||||||
int i = tablesize; // number of commands in table
|
|
||||||
|
|
||||||
while (i--) {
|
|
||||||
if (cmd[cursor] == table[i].opcode) { // lookup command in opcode table
|
|
||||||
cursor++; // strip 1 byte opcode
|
|
||||||
if ((cursor + table[i].params) <= cmdlen) {
|
|
||||||
memmove(foundcmd, cmd + cursor,
|
|
||||||
table[i].params); // strip opcode from cmd array
|
|
||||||
cursor += table[i].params;
|
|
||||||
ESP_LOGD(TAG, "MAC command %s", table[i].cmdname);
|
|
||||||
} else
|
|
||||||
ESP_LOGD(TAG, "MAC command 0x%02X with missing parameter(s)",
|
|
||||||
table[i].opcode);
|
|
||||||
break; // command found -> exit table lookup loop
|
|
||||||
} // end of command validation
|
|
||||||
} // end of command table lookup loop
|
|
||||||
if (i < 0) { // command not found -> skip it
|
|
||||||
ESP_LOGD(TAG, "Unknown MAC command 0x%02X", cmd[cursor]);
|
|
||||||
cursor++;
|
|
||||||
}
|
|
||||||
} // command parsing loop
|
|
||||||
|
|
||||||
} // mac_decode()
|
|
||||||
|
|
||||||
const char *getSfName(rps_t rps) {
|
const char *getSfName(rps_t rps) {
|
||||||
const char *const t[] = {"FSK", "SF7", "SF8", "SF9",
|
const char *const t[] = {"FSK", "SF7", "SF8", "SF9",
|
||||||
"SF10", "SF11", "SF12", "SF?"};
|
"SF10", "SF11", "SF12", "SF?"};
|
||||||
@ -611,4 +547,48 @@ u1_t os_getBattLevel() {
|
|||||||
} // getBattLevel()
|
} // getBattLevel()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if (VERBOSE)
|
||||||
|
// decode LORAWAN MAC message
|
||||||
|
void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, bool is_down) {
|
||||||
|
|
||||||
|
if (!cmdlen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint8_t foundcmd[cmdlen], cursor = 0;
|
||||||
|
|
||||||
|
// select CID resolve table
|
||||||
|
const mac_t *p;
|
||||||
|
p = is_down ? MACdn_table : MACup_table;
|
||||||
|
const int tablesize = is_down ? MACdn_tSize : MACup_tSize;
|
||||||
|
const String MACdir = is_down ? "-->" : "<--";
|
||||||
|
|
||||||
|
while (cursor < cmdlen) {
|
||||||
|
|
||||||
|
// get number of commands in CID table
|
||||||
|
int i = tablesize;
|
||||||
|
|
||||||
|
// lookup cmd in CID table
|
||||||
|
while (i--) {
|
||||||
|
if (cmd[cursor] == (p + i)->cid) { // lookup command in CID table
|
||||||
|
cursor++; // strip 1 byte CID
|
||||||
|
if ((cursor + (p + i)->params) <= cmdlen) {
|
||||||
|
memmove(foundcmd, cmd + cursor,
|
||||||
|
(p + i)->params); // strip opcode from cmd array
|
||||||
|
cursor += (p + i)->params;
|
||||||
|
ESP_LOGD(TAG, "%s MAC command %s", MACdir, (p + i)->cmdname);
|
||||||
|
} else
|
||||||
|
ESP_LOGD(TAG, "%s MAC command 0x%02X with missing parameter(s)",
|
||||||
|
MACdir, (p + i)->cid);
|
||||||
|
break; // command found -> exit table lookup loop
|
||||||
|
} // end of command validation
|
||||||
|
} // end of command table lookup loop
|
||||||
|
if (i < 0) { // command not found -> skip it
|
||||||
|
ESP_LOGD(TAG, "%s Unknown MAC command 0x%02X", MACdir, cmd[cursor]);
|
||||||
|
cursor++;
|
||||||
|
}
|
||||||
|
} // command parsing loop
|
||||||
|
|
||||||
|
} // mac_decode()
|
||||||
|
#endif // VERBOSE
|
||||||
|
|
||||||
#endif // HAS_LORA
|
#endif // HAS_LORA
|
@ -457,7 +457,7 @@ void setup() {
|
|||||||
clock_init();
|
clock_init();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER)
|
#if (TIME_SYNC_LORASERVER) || (TIME_SYNC_LORAWAN)
|
||||||
timesync_init(); // create loraserver time sync task
|
timesync_init(); // create loraserver time sync task
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
//
|
//
|
||||||
// Note: After editing, before "build", use "clean" button in PlatformIO!
|
// Note: After editing, before "build", use "clean" button in PlatformIO!
|
||||||
|
|
||||||
// Verbose enables serial output
|
// Verbose enables additional serial debug output
|
||||||
#define VERBOSE 0 // set to 0 to silence the device, for mute use build option
|
#define VERBOSE 0 // set to 0 to silence the device, for mute use build option
|
||||||
|
|
||||||
// Payload send cycle and encoding
|
// Payload send cycle and encoding
|
||||||
@ -74,15 +74,13 @@
|
|||||||
|
|
||||||
// settings for syncing time of node with a time source (network / gps / rtc / timeserver)
|
// settings for syncing time of node with a time source (network / gps / rtc / timeserver)
|
||||||
#define TIME_SYNC_LORAWAN 1 // set to 1 to use LORA network as time source, 0 means off [default = 1]
|
#define TIME_SYNC_LORAWAN 1 // set to 1 to use LORA network as time source, 0 means off [default = 1]
|
||||||
#define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source (GPS/LORA/RTC) [default = 60], 0 means off
|
|
||||||
#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off
|
|
||||||
#define TIME_SYNC_COMPILEDATE 0 // set to 1 to use compile date to initialize RTC after power outage [default = 0]
|
|
||||||
|
|
||||||
// specific settings for syncing time of node with a timeserver
|
|
||||||
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
|
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
|
||||||
|
#define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source [default = 60], 0 means off
|
||||||
|
#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off
|
||||||
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging, max. 255
|
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging, max. 255
|
||||||
#define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds]
|
#define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds]
|
||||||
#define TIME_SYNC_TIMEOUT 300 // timeout waiting for timeserver answer [seconds]
|
#define TIME_SYNC_TIMEOUT 300 // timeout waiting for timeserver answer [seconds]
|
||||||
|
#define TIME_SYNC_COMPILEDATE 0 // set to 1 to use compile date to initialize RTC after power outage [default = 0]
|
||||||
|
|
||||||
// time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino
|
// time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino
|
||||||
#define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time
|
#define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time
|
||||||
|
@ -343,7 +343,7 @@ void set_flush(uint8_t val[]) {
|
|||||||
// format: opcode, function, #bytes params,
|
// format: opcode, function, #bytes params,
|
||||||
// flag (true = do make settings persistent / false = don't)
|
// flag (true = do make settings persistent / false = don't)
|
||||||
//
|
//
|
||||||
static cmd_t table[] = {
|
static const cmd_t table[] = {
|
||||||
{0x01, set_rssi, 1, true}, {0x02, set_countmode, 1, true},
|
{0x01, set_rssi, 1, true}, {0x02, set_countmode, 1, true},
|
||||||
{0x03, set_gps, 1, true}, {0x04, set_display, 1, true},
|
{0x03, set_gps, 1, true}, {0x04, set_display, 1, true},
|
||||||
{0x05, set_loradr, 1, true}, {0x06, set_lorapower, 1, true},
|
{0x05, set_loradr, 1, true}, {0x06, set_lorapower, 1, true},
|
||||||
|
@ -28,6 +28,21 @@ void calibrateTime(void) {
|
|||||||
time_t t = 0;
|
time_t t = 0;
|
||||||
uint16_t t_msec = 0;
|
uint16_t t_msec = 0;
|
||||||
|
|
||||||
|
// kick off asychronous lora timesync if we have
|
||||||
|
#if (HAS_LORA) && (TIME_SYNC_LORASERVER) || (TIME_SYNC_LORAWAN)
|
||||||
|
timesync_sendReq();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// has RTC -> fallback to RTC time
|
||||||
|
#ifdef HAS_RTC
|
||||||
|
t = get_rtctime();
|
||||||
|
if (t) {
|
||||||
|
timeSource = _rtc;
|
||||||
|
goto finish;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 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);
|
||||||
@ -37,21 +52,8 @@ void calibrateTime(void) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// kick off asychronous lora timesync if we have
|
// no local time source -> don't set time
|
||||||
#if (HAS_LORA) && (TIME_SYNC_LORASERVER) || (TIME_SYNC_LORAWAN)
|
return;
|
||||||
send_timesync_req();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// no time from GPS -> fallback to RTC time while trying lora sync
|
|
||||||
#ifdef HAS_RTC
|
|
||||||
t = get_rtctime();
|
|
||||||
if (t) {
|
|
||||||
timeSource = _rtc;
|
|
||||||
goto finish;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
goto finish;
|
|
||||||
|
|
||||||
finish:
|
finish:
|
||||||
|
|
||||||
|
190
src/timesync.cpp
190
src/timesync.cpp
@ -11,44 +11,59 @@ You may use timesync option 2 if you do not want or cannot accept this.
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "timesync.h"
|
#if (HAS_LORA)
|
||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER) && (TIME_SYNC_LORAWAN) && (HAS_LORA)
|
#if (TIME_SYNC_LORASERVER) && (TIME_SYNC_LORAWAN)
|
||||||
#error Duplicate timesync method selected. You must select either LORASERVER or LORAWAN timesync.
|
#error Duplicate timesync method selected. You must select either LORASERVER or LORAWAN timesync.
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "timesync.h"
|
||||||
|
|
||||||
|
#define WRAP(v, top) (v++ > top ? 0 : v)
|
||||||
|
|
||||||
// Local logging tag
|
// Local logging tag
|
||||||
static const char TAG[] = __FILE__;
|
static const char TAG[] = __FILE__;
|
||||||
|
|
||||||
// timesync option 1: use external timeserver (for LoRAWAN < 1.0.3)
|
|
||||||
|
|
||||||
#if (TIME_SYNC_LORASERVER) && (HAS_LORA)
|
|
||||||
|
|
||||||
static TaskHandle_t timeSyncReqTask = NULL;
|
|
||||||
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),
|
||||||
static uint8_t sample_idx = 0;
|
sample_idx;
|
||||||
static uint32_t timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps] = {0};
|
static uint16_t timestamp_msec;
|
||||||
|
static uint32_t timestamp_sec,
|
||||||
|
timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps];
|
||||||
|
static TaskHandle_t timeSyncReqTask = NULL;
|
||||||
|
|
||||||
// send time request message
|
// create task for timeserver handshake processing, called from main.cpp
|
||||||
void send_timesync_req(void) {
|
void timesync_init() {
|
||||||
|
xTaskCreatePinnedToCore(timesync_processReq, // task function
|
||||||
|
"timesync_req", // name of task
|
||||||
|
2048, // stack size of task
|
||||||
|
(void *)1, // task parameter
|
||||||
|
3, // priority of the task
|
||||||
|
&timeSyncReqTask, // task handle
|
||||||
|
1); // CPU core
|
||||||
|
}
|
||||||
|
|
||||||
|
// kickoff asnychronous timesync handshake
|
||||||
|
void timesync_sendReq(void) {
|
||||||
// if a timesync handshake is pending then exit
|
// if a timesync handshake is pending then exit
|
||||||
if (timeSyncPending)
|
if (timeSyncPending)
|
||||||
return;
|
return;
|
||||||
// else unblock timesync task
|
// else clear array and unblock timesync task
|
||||||
else {
|
else {
|
||||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request seqNo#%d started",
|
||||||
|
millis() / 1000.0, time_sync_seqNo);
|
||||||
|
sample_idx = 0;
|
||||||
xTaskNotifyGive(timeSyncReqTask);
|
xTaskNotifyGive(timeSyncReqTask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// task for sending time sync requests
|
// task for processing time sync request
|
||||||
void IRAM_ATTR process_timesync_req(void *taskparameter) {
|
void IRAM_ATTR timesync_processReq(void *taskparameter) {
|
||||||
|
|
||||||
uint32_t rcv_seq_no = TIMEREQUEST_FINISH, time_offset_ms;
|
uint32_t rcv_seq_no = TIMEREQUEST_FINISH, 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 send_timesync_req(). 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
|
// recv_timesync_ans(), which is called from RX callback in lorawan.cpp, each
|
||||||
// time a timestamp from timeserver arrived.
|
// time a timestamp from timeserver arrived.
|
||||||
|
|
||||||
@ -66,14 +81,26 @@ void IRAM_ATTR process_timesync_req(void *taskparameter) {
|
|||||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||||
}
|
}
|
||||||
|
|
||||||
// trigger and collect timestamp samples
|
// clear timestamp array
|
||||||
|
timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps] = {0};
|
||||||
|
|
||||||
|
// trigger and collect samples in timestamp array
|
||||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||||
// send timesync request to timeserver
|
|
||||||
|
// send timesync request to timeserver or networkserver
|
||||||
|
#if (TIME_SYNC_LORASERVER)
|
||||||
|
// timesync option 1: use external 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)
|
||||||
|
// timesync option 2: use LoRAWAN network time (requires LoRAWAN >= 1.0.3)
|
||||||
|
LMIC_requestNetworkTime(DevTimeAns_Cb, &time_sync_seqNo);
|
||||||
|
// open a receive window to trigger DevTimeAns
|
||||||
|
LMIC_sendAlive();
|
||||||
|
#endif
|
||||||
|
|
||||||
// wait until recv_timesync_ans() signals a timestamp was received
|
// wait until a timestamp was received
|
||||||
while (rcv_seq_no != time_sync_seqNo) {
|
while (rcv_seq_no != time_sync_seqNo) {
|
||||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &rcv_seq_no,
|
if (xTaskNotifyWait(0x00, ULONG_MAX, &rcv_seq_no,
|
||||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
|
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
|
||||||
@ -84,29 +111,27 @@ void IRAM_ATTR process_timesync_req(void *taskparameter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate time diff from collected timestamps
|
// calculate time diff from 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 maybe wrap around seqNo, keeping it in time port range
|
||||||
time_sync_seqNo++;
|
WRAP(time_sync_seqNo, TIMEREQUEST_MAX_SEQNO);
|
||||||
if (time_sync_seqNo > TIMEREQUEST_MAX_SEQNO) {
|
|
||||||
time_sync_seqNo = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// increment index for timestamp array
|
// increment index for timestamp array
|
||||||
sample_idx++;
|
sample_idx++;
|
||||||
|
|
||||||
// if last cycle, send finish char for closing timesync handshake,
|
// if last cycle, finish after, else pause until next cycle
|
||||||
// else wait until time has come for next cycle
|
|
||||||
if (i < TIME_SYNC_SAMPLES - 1) { // wait for next cycle
|
if (i < TIME_SYNC_SAMPLES - 1) { // wait for next cycle
|
||||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||||
} else { // finish timesync handshake
|
} else {
|
||||||
|
#if (TIME_SYNC_LORASERVER)
|
||||||
|
// send finish char for closing timesync handshake
|
||||||
payload.reset();
|
payload.reset();
|
||||||
payload.addByte(TIMEREQUEST_FINISH);
|
payload.addByte(TIMEREQUEST_FINISH);
|
||||||
SendPayload(RCMDPORT, prio_high);
|
SendPayload(RCMDPORT, prio_high);
|
||||||
// open a receive window to get last time_sync_answer instantly
|
// open a receive window to get last time_sync_answer instantly
|
||||||
LMIC_sendAlive();
|
LMIC_sendAlive();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
} // end of for loop to collect timestamp samples
|
} // end of for loop to collect timestamp samples
|
||||||
@ -138,8 +163,8 @@ void IRAM_ATTR process_timesync_req(void *taskparameter) {
|
|||||||
} // infinite while(1)
|
} // infinite while(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// called from lorawan.cpp
|
// store incoming timestamps
|
||||||
void store_timestamp(uint32_t timestamp, timesync_t timestamp_type) {
|
void timesync_storeReq(uint32_t timestamp, timesync_t timestamp_type) {
|
||||||
|
|
||||||
ESP_LOGD(TAG, "[%0.3f] seq#%d[%d]: timestamp(t%d)=%d", millis() / 1000.0,
|
ESP_LOGD(TAG, "[%0.3f] seq#%d[%d]: timestamp(t%d)=%d", millis() / 1000.0,
|
||||||
time_sync_seqNo, sample_idx, timestamp_type, timestamp);
|
time_sync_seqNo, sample_idx, timestamp_type, timestamp);
|
||||||
@ -147,17 +172,18 @@ void store_timestamp(uint32_t timestamp, timesync_t timestamp_type) {
|
|||||||
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
|
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
// process timeserver timestamp answer, called by myRxCallback() in lorawan.cpp
|
#if (TIME_SYNC_LORASERVER)
|
||||||
int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
|
// evaluate timerserver's timestamp answer, called by myRxCallback() in
|
||||||
|
// lorawan.cpp
|
||||||
|
int recv_timeserver_ans(const uint8_t buf[], const uint8_t buf_len) {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
parse 7 byte timesync_answer:
|
parse 6 byte timesync_answer:
|
||||||
|
|
||||||
byte meaning
|
byte meaning
|
||||||
1 sequence number (taken from node's time_sync_req)
|
1 sequence number (taken from node's time_sync_req)
|
||||||
2 timezone in 15 minutes steps
|
2..5 current second (from epoch time 1970)
|
||||||
3..6 current second (from epoch time 1970)
|
6 1/250ths fractions of current second
|
||||||
7 1/250ths fractions of current second
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// if no timesync handshake is pending then exit
|
// if no timesync handshake is pending then exit
|
||||||
@ -165,12 +191,12 @@ int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
|
|||||||
return 0; // failure
|
return 0; // failure
|
||||||
|
|
||||||
// extract 1 byte timerequest sequence number from payload
|
// extract 1 byte timerequest sequence number from payload
|
||||||
uint8_t seq_no = buf[0];
|
uint8_t seqNo = buf[0];
|
||||||
buf++;
|
buf++;
|
||||||
|
|
||||||
// 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 (buf_len != TIME_SYNC_FRAME_LENGTH) {
|
||||||
if (seq_no == 0xff)
|
if (seqNo == 0xff)
|
||||||
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
|
||||||
@ -184,11 +210,6 @@ int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
|
|||||||
// pointers to 4 bytes msb order
|
// pointers to 4 bytes msb order
|
||||||
uint32_t timestamp_sec, *timestamp_ptr;
|
uint32_t timestamp_sec, *timestamp_ptr;
|
||||||
|
|
||||||
// extract 1 byte containing timezone offset
|
|
||||||
// one step being 15min * 60sec = 900sec
|
|
||||||
uint32_t timestamp_tzsec = buf[0] * 900; // timezone offset in secs
|
|
||||||
buf++;
|
|
||||||
|
|
||||||
// extract 4 bytes containing gateway time in UTC seconds since unix
|
// extract 4 bytes containing gateway time in UTC seconds since unix
|
||||||
// epoch and convert it to uint32_t, octet order is big endian
|
// epoch and convert it to uint32_t, octet order is big endian
|
||||||
timestamp_ptr = (uint32_t *)buf;
|
timestamp_ptr = (uint32_t *)buf;
|
||||||
@ -205,15 +226,14 @@ int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
|
|||||||
// we guess timepoint is recent if it is newer than code compile date
|
// we guess timepoint is recent if it is newer than code compile date
|
||||||
if (timeIsValid(t)) {
|
if (timeIsValid(t)) {
|
||||||
ESP_LOGD(TAG, "[%0.3f] Timesync request seq#%d rcvd at %0.3f",
|
ESP_LOGD(TAG, "[%0.3f] Timesync request seq#%d rcvd at %0.3f",
|
||||||
millis() / 1000.0, seq_no, osticks2ms(os_getTime()) / 1000.0);
|
millis() / 1000.0, seqNo, osticks2ms(os_getTime()) / 1000.0);
|
||||||
|
|
||||||
// store time received from gateway
|
// store time received from gateway
|
||||||
store_timestamp(timestamp_sec, gwtime_sec);
|
timesync_storeReq(timestamp_sec, gwtime_sec);
|
||||||
store_timestamp(timestamp_msec, gwtime_msec);
|
timesync_storeReq(timestamp_msec, gwtime_msec);
|
||||||
store_timestamp(timestamp_tzsec, gwtime_tzsec);
|
|
||||||
|
|
||||||
// inform processing task
|
// inform processing task
|
||||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
xTaskNotify(timeSyncReqTask, seqNo, eSetBits);
|
||||||
|
|
||||||
return 1; // success
|
return 1; // success
|
||||||
} else {
|
} else {
|
||||||
@ -224,31 +244,14 @@ int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create task for timeserver handshake processing, called from main.cpp
|
#elif (TIME_SYNC_LORAWAN)
|
||||||
void timesync_init() {
|
|
||||||
xTaskCreatePinnedToCore(process_timesync_req, // task function
|
|
||||||
"timesync_req", // name of task
|
|
||||||
2048, // stack size of task
|
|
||||||
(void *)1, // task parameter
|
|
||||||
3, // priority of the task
|
|
||||||
&timeSyncReqTask, // task handle
|
|
||||||
1); // CPU core
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
void IRAM_ATTR DevTimeAns_Cb(void *pUserData, int flagSuccess) {
|
||||||
|
// Explicit conversion from void* to uint8_t* to avoid compiler errors
|
||||||
|
uint8_t *seqNo = (uint8_t *)pUserData;
|
||||||
|
|
||||||
// timesync option 2: use LoRAWAN network time (requires LoRAWAN >= 1.0.3)
|
// mask application irq to ensure accurate timing
|
||||||
|
mask_user_IRQ();
|
||||||
#if (TIME_SYNC_LORAWAN) && (HAS_LORA)
|
|
||||||
|
|
||||||
// send time request message
|
|
||||||
void send_timesync_req(void) {
|
|
||||||
LMIC_requestNetworkTime(process_timesync_req, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void IRAM_ATTR process_timesync_req(void *pVoidUserUTCTime, int flagSuccess) {
|
|
||||||
// Explicit conversion from void* to uint32_t* to avoid compiler errors
|
|
||||||
time_t *pUserUTCTime = (time_t *)pVoidUserUTCTime;
|
|
||||||
|
|
||||||
// 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:
|
||||||
@ -259,31 +262,38 @@ void IRAM_ATTR process_timesync_req(void *pVoidUserUTCTime, int flagSuccess) {
|
|||||||
lmic_time_reference_t lmicTime;
|
lmic_time_reference_t lmicTime;
|
||||||
|
|
||||||
if (flagSuccess != 1) {
|
if (flagSuccess != 1) {
|
||||||
ESP_LOGW(TAG, "LoRaWAN network did not answer time request");
|
ESP_LOGW(TAG, "Network did not answer time request");
|
||||||
return;
|
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
|
||||||
flagSuccess = LMIC_getNetworkTimeReference(&lmicTime);
|
if ((LMIC_getNetworkTimeReference(&lmicTime)) != 1) {
|
||||||
if (flagSuccess != 1) {
|
ESP_LOGW(TAG, "Network time request failed");
|
||||||
ESP_LOGW(TAG, "LoRaWAN time request failed");
|
goto Finish;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// mask application irq to ensure accurate timing
|
// Calculate UTCTime, considering the difference between GPS and UTC time
|
||||||
mask_user_IRQ();
|
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);
|
||||||
|
|
||||||
// Update networkUTCTime, considering the difference between GPS and UTC time
|
// store time received from gateway
|
||||||
uint32_t networkTimeSec = lmicTime.tNetwork + GPS_UTC_DIFF;
|
timesync_storeReq(timestamp_sec, gwtime_sec);
|
||||||
// Add delay between the instant the time was transmitted and the current time
|
timesync_storeReq(timestamp_msec, gwtime_msec);
|
||||||
uint16_t requestDelaymSec =
|
|
||||||
osticks2ms(os_getTime() - lmicTime.tLocal);
|
|
||||||
|
|
||||||
// Update system time with time read from the network
|
// inform processing task
|
||||||
setMyTime(networkTimeSec, requestDelaymSec, _lora);
|
xTaskNotify(timeSyncReqTask, *seqNo, eSetBits);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
} // user_request_network_time_callback
|
#endif
|
||||||
#endif // TIME_SYNC_LORAWAN
|
|
||||||
|
#endif // HAS_LORA
|
Loading…
Reference in New Issue
Block a user