Merge pull request #570 from cyberman54/development
update branch sds011 to dev
This commit is contained in:
commit
ffa4c6d1c6
@ -54,7 +54,7 @@ Depending on board hardware following features are supported:
|
||||
- LED (shows power & status)
|
||||
- OLED Display (shows detailed 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
|
||||
- Battery voltage monitoring
|
||||
- GPS (Generic serial NMEA, or Quectel L76 I2C)
|
||||
|
@ -3,15 +3,37 @@
|
||||
|
||||
#include "cyclic.h"
|
||||
#include "qrcode.h"
|
||||
#define DISPLAY_PAGES (7) // number of paxcounter display pages
|
||||
|
||||
// settings for oled display library
|
||||
#define USE_BACKBUFFER 1
|
||||
#define MY_OLED OLED_128x64
|
||||
#define OLED_ADDR -1
|
||||
#define OLED_INVERT 0
|
||||
#define USE_HW_I2C 1
|
||||
|
||||
#ifndef DISPLAY_FLIP
|
||||
#define DISPLAY_FLIP 0
|
||||
#endif
|
||||
|
||||
// settings for qr code generator
|
||||
#define QR_VERSION 3 // 29 x 29px
|
||||
#define QR_SCALEFACTOR 2 // 29 -> 58x < 64px
|
||||
|
||||
// settings for curve plotter
|
||||
#define DISPLAY_WIDTH 128 // Width in pixels of OLED-display, must be 32X
|
||||
#define DISPLAY_HEIGHT 64 // Height in pixels of OLED-display, must be 64X
|
||||
|
||||
extern uint8_t DisplayIsOn, displaybuf[];
|
||||
|
||||
void setup_display(int contrast = 0);
|
||||
void refreshTheDisplay(bool nextPage = false);
|
||||
void init_display(bool verbose = false);
|
||||
void shutdown_display(void);
|
||||
void draw_page(time_t t, bool nextpage);
|
||||
void dp_printf(uint16_t x, uint16_t y, uint8_t font, uint8_t inv,
|
||||
const char *format, ...);
|
||||
void dp_dump(uint8_t *pBuffer);
|
||||
void dp_printqr(uint16_t offset_x, uint16_t offset_y, const char *Message);
|
||||
void oledfillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
|
||||
uint8_t bRender);
|
||||
|
@ -132,7 +132,6 @@ extern SemaphoreHandle_t I2Caccess;
|
||||
extern TaskHandle_t irqHandlerTask, ClockTask;
|
||||
extern TimerHandle_t WifiChanTimer;
|
||||
extern Timezone myTZ;
|
||||
extern time_t userUTCTime;
|
||||
extern RTC_DATA_ATTR runmode_t RTC_runmode;
|
||||
|
||||
// application includes
|
||||
|
@ -20,7 +20,7 @@ int gps_config();
|
||||
bool gps_hasfix();
|
||||
void gps_storelocation(gpsStatus_t *gps_store);
|
||||
void gps_loop(void *pvParameters);
|
||||
time_t fetch_gpsTime(uint16_t *msec);
|
||||
time_t fetch_gpsTime(void);
|
||||
time_t get_gpstime(uint16_t *msec);
|
||||
time_t get_gpstime(void);
|
||||
|
||||
#endif
|
@ -5,9 +5,6 @@
|
||||
#include "rcommand.h"
|
||||
#include "timekeeper.h"
|
||||
#include <driver/rtc_io.h>
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
#include "timesync.h"
|
||||
#endif
|
||||
|
||||
// LMIC-Arduino LoRaWAN Stack
|
||||
#include <lmic.h>
|
||||
@ -23,13 +20,6 @@
|
||||
|
||||
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);
|
||||
void lora_setupForNetwork(bool preJoin);
|
||||
void lmictask(void *pvParameters);
|
||||
@ -39,24 +29,56 @@ void get_hard_deveui(uint8_t *pdeveui);
|
||||
void os_getDevKey(u1_t *buf);
|
||||
void os_getArtEui(u1_t *buf);
|
||||
void os_getDevEui(u1_t *buf);
|
||||
void showLoraKeys(void);
|
||||
void lora_send(void *pvParameters);
|
||||
void lora_enqueuedata(MessageBuffer_t *message);
|
||||
void lora_queuereset(void);
|
||||
void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev);
|
||||
void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port,
|
||||
const uint8_t *pMsg, size_t nMsg);
|
||||
void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,
|
||||
size_t nMsg);
|
||||
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 *getBwName(rps_t rps);
|
||||
const char *getCrName(rps_t rps);
|
||||
// u1_t os_getBattLevel(void);
|
||||
|
||||
#if (TIME_SYNC_LORAWAN)
|
||||
void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
int flagSuccess);
|
||||
#endif
|
||||
#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
|
@ -5,6 +5,7 @@
|
||||
#include "rtctime.h"
|
||||
#include "TimeLib.h"
|
||||
#include "irqhandler.h"
|
||||
#include "timesync.h"
|
||||
|
||||
#if (HAS_GPS)
|
||||
#include "gpsread.h"
|
||||
@ -31,7 +32,5 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec, timesource_t mytimesou
|
||||
time_t compiledUTC(void);
|
||||
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
|
||||
int8_t rxPin, int8_t txPins);
|
||||
time_t TimeSyncAns(uint8_t seqNo, uint64_t unixTime);
|
||||
void TimeSyncReq(uint8_t seqNo);
|
||||
|
||||
#endif // _timekeeper_H
|
@ -1,22 +1,28 @@
|
||||
#ifndef _TIMESYNC_H
|
||||
#define _TIMESYNC_H
|
||||
|
||||
#include <chrono>
|
||||
#include "globals.h"
|
||||
#include "irqhandler.h"
|
||||
#include "timekeeper.h"
|
||||
|
||||
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
|
||||
#define TIME_SYNC_FRAME_LENGTH 0x07 // timeserver answer frame length [bytes]
|
||||
#define TIME_SYNC_FIXUP 4 // calibration to fixup processing time [milliseconds]
|
||||
#define TIMEREQUEST_MAX_SEQNO 0xf0 // threshold for wrap around seqno
|
||||
#define TIME_SYNC_FRAME_LENGTH 6 // timeserver answer frame length [bytes]
|
||||
#define TIME_SYNC_FIXUP 16 // compensation for processing time [milliseconds]
|
||||
#define TIME_SYNC_MAX_SEQNO 0xfe // threshold for wrap around time_sync_seqNo
|
||||
#define TIME_SYNC_END_FLAG (TIME_SYNC_MAX_SEQNO + 1) // end of handshake marker
|
||||
#define GPS_UTC_DIFF 315964800 // seconds diff between gps and utc epoch
|
||||
|
||||
enum timesync_t {
|
||||
timesync_tx,
|
||||
timesync_rx,
|
||||
gwtime_sec,
|
||||
gwtime_msec,
|
||||
no_of_timestamps
|
||||
};
|
||||
|
||||
void timesync_init(void);
|
||||
void send_timesync_req(void);
|
||||
|
||||
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);
|
||||
void timesync_request(void);
|
||||
void timesync_store(uint32_t timestamp, timesync_t timestamp_type);
|
||||
void IRAM_ATTR timesync_processReq(void *taskparameter);
|
||||
void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag);
|
||||
|
||||
#endif
|
||||
|
@ -45,7 +45,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I
|
||||
|
||||
[common]
|
||||
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
||||
release_version = 1.9.91
|
||||
release_version = 1.9.96
|
||||
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
|
||||
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
|
||||
debug_level = 3
|
||||
@ -59,8 +59,8 @@ upload_speed = 115200
|
||||
lib_deps_lora =
|
||||
MCCI LoRaWAN LMIC library@>=3.1.0 ; MCCI LMIC by Terrill Moore
|
||||
lib_deps_display =
|
||||
ss_oled@>=3.3.1 ; simple and small OLED lib by Larry Bank
|
||||
BitBang_I2C@>=1.3.0
|
||||
ss_oled@4.0.1 ; simple and small OLED lib by Larry Bank
|
||||
BitBang_I2C@2.0.0
|
||||
QRCode@>=0.0.1
|
||||
lib_deps_matrix_display =
|
||||
Ultrathin_LED_Matrix@>=1.0.0
|
||||
|
@ -4,8 +4,6 @@
|
||||
function Decoder(bytes, port) {
|
||||
|
||||
var decoded = {};
|
||||
decoded.wifi = 0;
|
||||
decoded.ble = 0;
|
||||
|
||||
if (bytes.length === 0) {
|
||||
return {};
|
||||
|
@ -4,14 +4,24 @@
|
||||
function Converter(decoded, port) {
|
||||
|
||||
var converted = decoded;
|
||||
var pax = 0;
|
||||
|
||||
if (port === 1) {
|
||||
converted.pax = converted.ble + converted.wifi;
|
||||
|
||||
if ('wifi' in converted) {
|
||||
pax += converted.wifi
|
||||
}
|
||||
if ('ble' in converted) {
|
||||
pax += converted.ble
|
||||
}
|
||||
converted.pax = pax;
|
||||
|
||||
if (converted.hdop) {
|
||||
converted.hdop /= 100;
|
||||
converted.latitude /= 1000000;
|
||||
converted.longitude /= 1000000;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return converted;
|
||||
|
@ -3,8 +3,6 @@
|
||||
|
||||
function Decoder(bytes, port) {
|
||||
var decoded = {};
|
||||
decoded.wifi = 0;
|
||||
decoded.ble = 0;
|
||||
|
||||
if (port === 1) {
|
||||
var i = 0;
|
||||
|
@ -1,4 +1,41 @@
|
||||
[
|
||||
{
|
||||
"id": "b8bd33fd.61caa",
|
||||
"type": "function",
|
||||
"z": "449c1517.e25f4c",
|
||||
"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);\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,
|
||||
"noerr": 0,
|
||||
"x": 330,
|
||||
"y": 327,
|
||||
"wires": [
|
||||
[
|
||||
"c9a83ac9.50fd18",
|
||||
"6aeb3720.a89618",
|
||||
"6ac55bbe.12ac54"
|
||||
],
|
||||
[
|
||||
"de908e66.b6fd3"
|
||||
],
|
||||
[
|
||||
"d5a35bab.44cb18"
|
||||
],
|
||||
[
|
||||
"3a661f0a.c61b1"
|
||||
],
|
||||
[
|
||||
"9b4f492d.fbfd18"
|
||||
]
|
||||
],
|
||||
"outputLabels": [
|
||||
"gw_eui",
|
||||
"offset_ms",
|
||||
"device",
|
||||
"seq_no",
|
||||
"time_sync_ans"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9b4f492d.fbfd18",
|
||||
"type": "change",
|
||||
@ -177,49 +214,12 @@
|
||||
"id": "15980d22.6f4663",
|
||||
"type": "comment",
|
||||
"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.",
|
||||
"x": 160,
|
||||
"x": 150,
|
||||
"y": 47,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "b8bd33fd.61caa",
|
||||
"type": "function",
|
||||
"z": "449c1517.e25f4c",
|
||||
"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];",
|
||||
"outputs": 5,
|
||||
"noerr": 0,
|
||||
"x": 330,
|
||||
"y": 327,
|
||||
"wires": [
|
||||
[
|
||||
"c9a83ac9.50fd18",
|
||||
"6aeb3720.a89618",
|
||||
"6ac55bbe.12ac54"
|
||||
],
|
||||
[
|
||||
"de908e66.b6fd3"
|
||||
],
|
||||
[
|
||||
"d5a35bab.44cb18"
|
||||
],
|
||||
[
|
||||
"3a661f0a.c61b1"
|
||||
],
|
||||
[
|
||||
"9b4f492d.fbfd18"
|
||||
]
|
||||
],
|
||||
"outputLabels": [
|
||||
"gw_eui",
|
||||
"offset_ms",
|
||||
"device",
|
||||
"seq_no",
|
||||
"time_sync_ans"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "c9a83ac9.50fd18",
|
||||
"type": "debug",
|
||||
@ -346,7 +346,7 @@
|
||||
"type": "debug",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "",
|
||||
"active": true,
|
||||
"active": false,
|
||||
"tosidebar": true,
|
||||
"console": false,
|
||||
"tostatus": false,
|
||||
|
86
src/Timeserver/timeserver.java
Normal file
86
src/Timeserver/timeserver.java
Normal file
@ -0,0 +1,86 @@
|
||||
/* LoRaWAN Timeserver
|
||||
|
||||
VERSION: 1.3
|
||||
|
||||
construct 6 byte timesync_answer from gateway timestamp and node's time_sync_req
|
||||
|
||||
byte meaning
|
||||
1 sequence number (taken from node's time_sync_req)
|
||||
2..5 current second (from GPS epoch starting 1980)
|
||||
6 1/250ths fractions of current second
|
||||
|
||||
*/
|
||||
|
||||
function timecompare(a, b) {
|
||||
|
||||
const timeA = a.time;
|
||||
const timeB = b.time;
|
||||
|
||||
let comparison = 0;
|
||||
if (timeA > timeB) {
|
||||
comparison = 1;
|
||||
} else if (timeA < timeB) {
|
||||
comparison = -1;
|
||||
}
|
||||
return comparison;
|
||||
}
|
||||
|
||||
let confidence = 1000; // max millisecond diff gateway time to server time
|
||||
|
||||
// guess if we have received a valid time_sync_req command
|
||||
if (msg.payload.payload_raw.length != 1)
|
||||
return;
|
||||
|
||||
var deviceMsg = { payload: msg.payload.dev_id };
|
||||
var seqNo = msg.payload.payload_raw[0];
|
||||
var seqNoMsg = { payload: seqNo };
|
||||
var gateway_list = msg.payload.metadata.gateways;
|
||||
|
||||
// filter all gateway timestamps that have milliseconds part (which we assume have a ".")
|
||||
var gateways = gateway_list.filter(function (element) {
|
||||
return (element.time.includes("."));
|
||||
});
|
||||
|
||||
var gateway_time = gateways.map(gw => {
|
||||
return {
|
||||
time: new Date(gw.time),
|
||||
eui: gw.gtw_id,
|
||||
}
|
||||
});
|
||||
var server_time = new Date(msg.payload.metadata.time);
|
||||
|
||||
// validate all gateway timestamps against lorawan server_time (which is assumed to be recent)
|
||||
var gw_timestamps = gateway_time.filter(function (element) {
|
||||
return ((element.time > (server_time - confidence) && element.time <= server_time));
|
||||
});
|
||||
|
||||
// if no timestamp left, we have no valid one and exit
|
||||
if (gw_timestamps.length === 0) {
|
||||
var notavailMsg = { payload: "n/a" };
|
||||
var notimeMsg = { payload: 0xff };
|
||||
var buf2 = Buffer.alloc(1);
|
||||
msg.payload = new Buffer(buf2.fill(0xff));
|
||||
msg.port = 9; // Paxcounter TIMEPORT
|
||||
return [notavailMsg, notavailMsg, deviceMsg, seqNoMsg, msg];}
|
||||
|
||||
// sort time array in ascending order to find most recent timestamp for time answer
|
||||
gw_timestamps.sort(timecompare);
|
||||
|
||||
var timestamp = gw_timestamps[0].time;
|
||||
var eui = gw_timestamps[0].eui;
|
||||
var offset = server_time - timestamp;
|
||||
|
||||
var seconds = Math.floor(timestamp/1000);
|
||||
var fractions = (timestamp % 1000) / 4;
|
||||
|
||||
let buf = new ArrayBuffer(6);
|
||||
new DataView(buf).setUint8(0, seqNo);
|
||||
new DataView(buf).setUint32(1, seconds);
|
||||
new DataView(buf).setUint8(5, fractions);
|
||||
|
||||
msg.payload = new Buffer(new Uint8Array(buf));
|
||||
msg.port = 9; // Paxcounter TIMEPORT
|
||||
var euiMsg = { payload: eui };
|
||||
var offsetMsg = { payload: offset };
|
||||
|
||||
return [euiMsg, offsetMsg, deviceMsg, seqNoMsg, msg];
|
104
src/display.cpp
104
src/display.cpp
@ -39,19 +39,6 @@ FONT_STRETCHED: 16x32px = 8 chars / line
|
||||
// local Tag for logging
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
#define DISPLAY_PAGES (6) // number of paxcounter display pages
|
||||
|
||||
// settings for oled display library
|
||||
#define USE_BACKBUFFER
|
||||
|
||||
// settings for qr code generator
|
||||
#define QR_VERSION 3 // 29 x 29px
|
||||
#define QR_SCALEFACTOR 2 // 29 -> 58x < 64px
|
||||
|
||||
// settings for curve plotter
|
||||
#define DISPLAY_WIDTH 128 // Width in pixels of OLED-display, must be 32X
|
||||
#define DISPLAY_HEIGHT 64 // Height in pixels of OLED-display, must be 64X
|
||||
|
||||
// helper array for converting month values to text
|
||||
const char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
@ -60,6 +47,23 @@ uint8_t displaybuf[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8] = {0};
|
||||
static uint8_t plotbuf[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8] = {0};
|
||||
|
||||
QRCode qrcode;
|
||||
SSOLED ssoled;
|
||||
|
||||
void setup_display(int contrast) {
|
||||
int rc = oledInit(&ssoled, MY_OLED, OLED_ADDR, DISPLAY_FLIP, OLED_INVERT,
|
||||
USE_HW_I2C, MY_OLED_SDA, MY_OLED_SCL, MY_OLED_RST,
|
||||
400000L); // use standard I2C bus at 400Khz
|
||||
|
||||
assert(rc != OLED_NOT_FOUND);
|
||||
|
||||
// set display buffer
|
||||
oledSetBackBuffer(&ssoled, displaybuf);
|
||||
if (contrast)
|
||||
oledSetContrast(&ssoled, contrast);
|
||||
|
||||
// clear display
|
||||
oledFill(&ssoled, 0, 1);
|
||||
}
|
||||
|
||||
void init_display(bool verbose) {
|
||||
|
||||
@ -68,19 +72,7 @@ void init_display(bool verbose) {
|
||||
ESP_LOGV(TAG, "[%0.3f] i2c mutex lock failed", millis() / 1000.0);
|
||||
else {
|
||||
|
||||
// init display
|
||||
#ifndef DISPLAY_FLIP
|
||||
oledInit(OLED_128x64, false, false, -1, -1, MY_OLED_RST, 400000L);
|
||||
#else
|
||||
oledInit(OLED_128x64, true, false, -1, -1, MY_OLED_RST, 400000L);
|
||||
#endif
|
||||
|
||||
// set display buffer
|
||||
oledSetBackBuffer(displaybuf);
|
||||
|
||||
// clear display
|
||||
oledSetContrast(DISPLAYCONTRAST);
|
||||
oledFill(0, 1);
|
||||
setup_display(DISPLAYCONTRAST);
|
||||
|
||||
if (verbose) {
|
||||
|
||||
@ -104,9 +96,9 @@ void init_display(bool verbose) {
|
||||
: "ext.");
|
||||
|
||||
// give user some time to read or take picture
|
||||
oledDumpBuffer(displaybuf);
|
||||
dp_dump(displaybuf);
|
||||
delay(2000);
|
||||
oledFill(0x00, 1);
|
||||
oledFill(&ssoled, 0x00, 1);
|
||||
#endif // VERBOSE
|
||||
|
||||
#if (HAS_LORA)
|
||||
@ -117,7 +109,7 @@ void init_display(bool verbose) {
|
||||
snprintf(deveui, 17, "%016llX", *((uint64_t *)&buf));
|
||||
|
||||
// display DEVEUI as QR code on the left
|
||||
oledSetContrast(30);
|
||||
oledSetContrast(&ssoled, 30);
|
||||
dp_printqr(3, 3, deveui);
|
||||
|
||||
// display DEVEUI as plain text on the right
|
||||
@ -127,15 +119,15 @@ void init_display(bool verbose) {
|
||||
dp_printf(80, i + 3, FONT_NORMAL, 0, "%4.4s", deveui + i * 4);
|
||||
|
||||
// give user some time to read or take picture
|
||||
oledDumpBuffer(displaybuf);
|
||||
dp_dump(displaybuf);
|
||||
delay(8000);
|
||||
oledSetContrast(DISPLAYCONTRAST);
|
||||
oledFill(0x00, 1);
|
||||
oledSetContrast(&ssoled, DISPLAYCONTRAST);
|
||||
oledFill(&ssoled, 0x00, 1);
|
||||
#endif // HAS_LORA
|
||||
|
||||
} // verbose
|
||||
|
||||
oledPower(cfg.screenon); // set display off if disabled
|
||||
oledPower(&ssoled, cfg.screenon); // set display off if disabled
|
||||
|
||||
I2C_MUTEX_UNLOCK(); // release i2c bus access
|
||||
} // mutex
|
||||
@ -164,7 +156,7 @@ void refreshTheDisplay(bool nextPage) {
|
||||
// set display on/off according to current device configuration
|
||||
if (DisplayIsOn != cfg.screenon) {
|
||||
DisplayIsOn = cfg.screenon;
|
||||
oledPower(cfg.screenon);
|
||||
oledPower(&ssoled, cfg.screenon);
|
||||
}
|
||||
|
||||
#ifndef HAS_BUTTON
|
||||
@ -176,7 +168,7 @@ void refreshTheDisplay(bool nextPage) {
|
||||
#endif
|
||||
|
||||
draw_page(t, nextPage);
|
||||
oledDumpBuffer(displaybuf);
|
||||
dp_dump(displaybuf);
|
||||
|
||||
I2C_MUTEX_UNLOCK(); // release i2c bus access
|
||||
|
||||
@ -214,7 +206,7 @@ start:
|
||||
|
||||
if (nextpage) {
|
||||
DisplayPage = (DisplayPage >= DISPLAY_PAGES - 1) ? 0 : (DisplayPage + 1);
|
||||
oledFill(0, 1);
|
||||
oledFill(&ssoled, 0, 1);
|
||||
}
|
||||
|
||||
switch (DisplayPage) {
|
||||
@ -224,7 +216,8 @@ start:
|
||||
// page 2: GPS
|
||||
// page 3: BME280/680
|
||||
// page 4: time
|
||||
// page 5: blank screen
|
||||
// page 5: lorawan parameters
|
||||
// page 6: blank screen
|
||||
|
||||
// page 0: parameters overview
|
||||
case 0:
|
||||
@ -306,7 +299,7 @@ start:
|
||||
|
||||
// page 1: pax graph
|
||||
case 1:
|
||||
oledDumpBuffer(plotbuf);
|
||||
dp_dump(plotbuf);
|
||||
break; // page1
|
||||
|
||||
// page 2: GPS
|
||||
@ -362,10 +355,35 @@ start:
|
||||
second(t));
|
||||
break;
|
||||
|
||||
// page 5: blank screen
|
||||
// page 5: lorawan parameters
|
||||
case 5:
|
||||
|
||||
#if (HAS_LORA)
|
||||
// 3|NtwkID:000000 TXpw:aa
|
||||
// 4|DevAdd:00000000 DR:0
|
||||
// 5|CHMsk:0000 Nonce:0000
|
||||
// 6|CUp:000000 CDn:000000
|
||||
// 7|SNR:-0000 RSSI:-0000
|
||||
dp_printf(0, 3, FONT_SMALL, 0, "NetwID:%06X TXpw:%-2d",
|
||||
LMIC.netid & 0x001FFFFF, LMIC.radio_txpow);
|
||||
dp_printf(0, 4, FONT_SMALL, 0, "DevAdd:%08X DR:%1d", LMIC.devaddr,
|
||||
LMIC.datarate);
|
||||
dp_printf(0, 5, FONT_SMALL, 0, "ChMsk:%04X Nonce:%04X", LMIC.channelMap,
|
||||
LMIC.devNonce);
|
||||
dp_printf(0, 6, FONT_SMALL, 0, "fUp:%-6d fDn:%-6d",
|
||||
LMIC.seqnoUp ? LMIC.seqnoUp - 1 : 0,
|
||||
LMIC.seqnoDn ? LMIC.seqnoDn - 1 : 0);
|
||||
dp_printf(0, 7, FONT_SMALL, 0, "SNR:%-5d RSSI:%-5d", (LMIC.snr + 2) / 4,
|
||||
LMIC.rssi);
|
||||
break; // page5
|
||||
#else // don't show blank page if we are unattended
|
||||
DisplayPage++; // next page
|
||||
#endif // HAS_LORA
|
||||
|
||||
// page 6: blank screen
|
||||
case 6:
|
||||
#ifdef HAS_BUTTON
|
||||
oledFill(0, 1);
|
||||
oledFill(&ssoled, 0, 1);
|
||||
break;
|
||||
#else // don't show blank page if we are unattended
|
||||
DisplayPage++; // next page
|
||||
@ -402,12 +420,14 @@ void dp_printf(uint16_t x, uint16_t y, uint8_t font, uint8_t inv,
|
||||
len = vsnprintf(temp, len + 1, format, arg);
|
||||
}
|
||||
va_end(arg);
|
||||
oledWriteString(0, x, y, temp, font, inv, false);
|
||||
oledWriteString(&ssoled, 0, x, y, temp, font, inv, false);
|
||||
if (temp != loc_buf) {
|
||||
free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void dp_dump(uint8_t *pBuffer) { oledDumpBuffer(&ssoled, pBuffer); }
|
||||
|
||||
void dp_printqr(uint16_t offset_x, uint16_t offset_y, const char *Message) {
|
||||
uint8_t qrcodeData[qrcode_getBufferSize(QR_VERSION)];
|
||||
qrcode_initText(&qrcode, qrcodeData, QR_VERSION, ECC_HIGH, Message);
|
||||
@ -434,7 +454,7 @@ void dp_printqr(uint16_t offset_x, uint16_t offset_y, const char *Message) {
|
||||
void oledfillRect(uint16_t x, uint16_t y, uint16_t width, uint16_t height,
|
||||
uint8_t bRender) {
|
||||
for (uint16_t xi = x; xi < x + width; xi++)
|
||||
oledDrawLine(xi, y, xi, y + height - 1, bRender);
|
||||
oledDrawLine(&ssoled, xi, y, xi, y + height - 1, bRender);
|
||||
}
|
||||
|
||||
int oledDrawPixel(uint8_t *buf, const uint16_t x, const uint16_t y,
|
||||
|
@ -91,7 +91,7 @@ bool gps_hasfix() {
|
||||
}
|
||||
|
||||
// function to fetch current time from struct; note: this is costly
|
||||
time_t fetch_gpsTime(uint16_t *msec) {
|
||||
time_t get_gpstime(uint16_t *msec) {
|
||||
|
||||
time_t time_sec = 0;
|
||||
|
||||
@ -132,11 +132,11 @@ time_t fetch_gpsTime(uint16_t *msec) {
|
||||
|
||||
return timeIsValid(time_sec);
|
||||
|
||||
} // fetch_gpsTime()
|
||||
} // get_gpstime()
|
||||
|
||||
time_t fetch_gpsTime(void) {
|
||||
time_t get_gpstime(void) {
|
||||
uint16_t msec;
|
||||
return fetch_gpsTime(&msec);
|
||||
return get_gpstime(&msec);
|
||||
}
|
||||
|
||||
// GPS serial feed FreeRTos Task
|
||||
|
@ -21,10 +21,7 @@
|
||||
#define LMIC_USE_INTERRUPTS 1
|
||||
|
||||
// time sync via LoRaWAN network, note: not supported by TTNv2
|
||||
//#define LMIC_ENABLE_DeviceTimeReq 1
|
||||
|
||||
// use callback event handlers, not onEvent() reference
|
||||
#define LMIC_ENABLE_onEvent 0
|
||||
#define LMIC_ENABLE_DeviceTimeReq 1
|
||||
|
||||
// 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,
|
||||
|
265
src/lorawan.cpp
265
src/lorawan.cpp
@ -23,29 +23,6 @@ RTC_NOINIT_ATTR int RTCseqnoUp, RTCseqnoDn;
|
||||
QueueHandle_t LoraSendQueue;
|
||||
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 {
|
||||
|
||||
public:
|
||||
@ -98,9 +75,9 @@ void lora_setupForNetwork(bool preJoin) {
|
||||
if (!cfg.adrmode)
|
||||
LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower);
|
||||
// show current devaddr
|
||||
ESP_LOGI(TAG, "DEVaddr: 0x%08X | Network ID: 0x%03X | Network Type: %d",
|
||||
ESP_LOGI(TAG, "DEVaddr: 0x%08X | Network ID: 0x%06X | Network Type: %d",
|
||||
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",
|
||||
getSfName(updr2rps(LMIC.datarate)),
|
||||
getBwName(updr2rps(LMIC.datarate)),
|
||||
@ -261,12 +238,22 @@ void lora_send(void *pvParameters) {
|
||||
// attempt to transmit payload
|
||||
else {
|
||||
|
||||
// switch (LMIC_sendWithCallback_strict(
|
||||
switch (LMIC_sendWithCallback(
|
||||
SendBuffer.MessagePort, SendBuffer.Message, SendBuffer.MessageSize,
|
||||
(cfg.countermode & 0x02), myTxCallback, NULL)) {
|
||||
switch (LMIC_setTxData2_strict(SendBuffer.MessagePort, SendBuffer.Message,
|
||||
SendBuffer.MessageSize,
|
||||
(cfg.countermode & 0x02))) {
|
||||
|
||||
case LMIC_ERROR_SUCCESS:
|
||||
// save current Fcnt to RTC RAM
|
||||
RTCseqnoUp = LMIC.seqnoUp;
|
||||
RTCseqnoDn = LMIC.seqnoDn;
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// if last packet sent was a timesync request, store TX timestamp
|
||||
if (SendBuffer.MessagePort == TIMEPORT)
|
||||
// store LMIC time when we started transmit of timesync request
|
||||
timesync_store(osticks2ms(os_getTime()), timesync_tx);
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize);
|
||||
break;
|
||||
case LMIC_ERROR_TX_BUSY: // LMIC already has a tx message pending
|
||||
@ -369,56 +356,6 @@ void lora_enqueuedata(MessageBuffer_t *message) {
|
||||
|
||||
void lora_queuereset(void) { xQueueReset(LoraSendQueue); }
|
||||
|
||||
#if (TIME_SYNC_LORAWAN)
|
||||
void IRAM_ATTR user_request_network_time_callback(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.
|
||||
// It contains the following fields:
|
||||
// - tLocal: the value returned by os_GetTime() when the time
|
||||
// request was sent to the gateway, and
|
||||
// - tNetwork: the seconds between the GPS epoch and the time
|
||||
// the gateway received the time request
|
||||
lmic_time_reference_t lmicTimeReference;
|
||||
|
||||
if (flagSuccess != 1) {
|
||||
ESP_LOGW(TAG, "LoRaWAN network did not answer time request");
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate lmic_time_reference
|
||||
flagSuccess = LMIC_getNetworkTimeReference(&lmicTimeReference);
|
||||
if (flagSuccess != 1) {
|
||||
ESP_LOGW(TAG, "LoRaWAN time request failed");
|
||||
return;
|
||||
}
|
||||
|
||||
// mask application irq to ensure accurate timing
|
||||
mask_user_IRQ();
|
||||
|
||||
// Update userUTCTime, considering the difference between the GPS and UTC
|
||||
// time, and the leap seconds until year 2019
|
||||
*pUserUTCTime = lmicTimeReference.tNetwork + 315964800;
|
||||
// Current time, in ticks
|
||||
ostime_t ticksNow = os_getTime();
|
||||
// Time when the request was sent, in ticks
|
||||
ostime_t ticksRequestSent = lmicTimeReference.tLocal;
|
||||
// Add the delay between the instant the time was transmitted and
|
||||
// the current time
|
||||
time_t requestDelaySec = osticks2ms(ticksNow - ticksRequestSent) / 1000;
|
||||
|
||||
// Update system time with time read from the network
|
||||
setMyTime(*pUserUTCTime + requestDelaySec, 0, _lora);
|
||||
|
||||
finish:
|
||||
// end of time critical section: release app irq lock
|
||||
unmask_user_IRQ();
|
||||
|
||||
} // user_request_network_time_callback
|
||||
#endif // TIME_SYNC_LORAWAN
|
||||
|
||||
// LMIC lorawan stack task
|
||||
void lmictask(void *pvParameters) {
|
||||
configASSERT(((uint32_t)pvParameters) == 1);
|
||||
@ -467,6 +404,15 @@ void myEventCallback(void *pUserData, ev_t ev) {
|
||||
|
||||
// process current event message
|
||||
switch (ev) {
|
||||
|
||||
case EV_TXCOMPLETE:
|
||||
// -> processed in lora_send()
|
||||
break;
|
||||
|
||||
case EV_RXCOMPLETE:
|
||||
// -> processed in myRxCallback()
|
||||
break;
|
||||
|
||||
case EV_JOINING:
|
||||
// do the network-specific setup prior to join.
|
||||
lora_setupForNetwork(true);
|
||||
@ -477,12 +423,6 @@ void myEventCallback(void *pUserData, ev_t ev) {
|
||||
lora_setupForNetwork(false);
|
||||
break;
|
||||
|
||||
case EV_TXCOMPLETE:
|
||||
// save current Fcnt to RTC RAM
|
||||
RTCseqnoUp = LMIC.seqnoUp;
|
||||
RTCseqnoDn = LMIC.seqnoDn;
|
||||
break;
|
||||
|
||||
case EV_JOIN_TXCOMPLETE:
|
||||
// replace descriptor from library with more descriptive term
|
||||
snprintf(lmic_event_msg, LMIC_EVENTMSG_LEN, "%-16s", "JOIN_WAIT");
|
||||
@ -500,105 +440,68 @@ void myEventCallback(void *pUserData, ev_t ev) {
|
||||
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,
|
||||
size_t nMsg) {
|
||||
|
||||
// display type of received data
|
||||
// display amount of received data
|
||||
if (nMsg)
|
||||
ESP_LOGI(TAG, "Received %u byte(s) of payload on port %u", nMsg, port);
|
||||
else if (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) {
|
||||
|
||||
// ignore mac messages
|
||||
// decode mac messages if we want to print those
|
||||
#if (VERBOSE)
|
||||
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
|
||||
case RCMDPORT:
|
||||
rcommand(pMsg, nMsg);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
// timeserver answer -> call timesync processor
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// valid timesync answer -> call timesync processor
|
||||
if (port == TIMEPORT) {
|
||||
recv_timesync_ans(pMsg, nMsg);
|
||||
break;
|
||||
}
|
||||
case TIMEPORT:
|
||||
// get and store gwtime from payload
|
||||
timesync_serverAnswer(const_cast<uint8_t *>(pMsg), nMsg);
|
||||
#endif
|
||||
|
||||
// unknown port -> display info
|
||||
ESP_LOGI(TAG, "Received data on unsupported port %u", port);
|
||||
// decode any piggybacked downlink MAC commands if we want to print those
|
||||
default:
|
||||
#if (VERBOSE)
|
||||
if (LMIC.dataBeg > 1)
|
||||
mac_decode(LMIC.frame, LMIC.dataBeg - 1, true);
|
||||
#endif // VERBOSE
|
||||
|
||||
break;
|
||||
} // switch
|
||||
}
|
||||
|
||||
// transmit complete message handler
|
||||
/*
|
||||
// event EV_TXCOMPLETE message handler
|
||||
void myTxCallback(void *pUserData, int fSuccess) {
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// if last packet sent was a timesync request, store TX timestamp
|
||||
if (LMIC.pendTxPort == TIMEPORT)
|
||||
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
|
||||
#endif
|
||||
uint8_t *const pMsg = (uint8_t *)pUserData;
|
||||
|
||||
// LMIC did successful transmit data
|
||||
if (fSuccess) {
|
||||
RTCseqnoUp = LMIC.seqnoUp;
|
||||
RTCseqnoDn = LMIC.seqnoDn;
|
||||
} else {
|
||||
// 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 *const t[] = {"FSK", "SF7", "SF8", "SF9",
|
||||
@ -618,7 +521,7 @@ const char *getCrName(rps_t rps) {
|
||||
|
||||
/*
|
||||
u1_t os_getBattLevel() {
|
||||
|
||||
|
||||
//return values:
|
||||
//MCMD_DEVS_EXT_POWER = 0x00, // external power supply
|
||||
//MCMD_DEVS_BATT_MIN = 0x01, // min battery value
|
||||
@ -642,4 +545,48 @@ u1_t os_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 %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
|
@ -33,7 +33,7 @@ IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
|
||||
|
||||
lmictask 1 2 MCCI LMiC LORAWAN stack
|
||||
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
|
||||
gpsloop 1 1 reads data from GPS via serial or i2c
|
||||
lorasendtask 1 1 feeds data from lora sendqueue to lmcic
|
||||
@ -87,7 +87,6 @@ hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL, *matrixDisplayIRQ = NULL;
|
||||
TaskHandle_t irqHandlerTask = NULL, ClockTask = NULL;
|
||||
SemaphoreHandle_t I2Caccess;
|
||||
bool volatile TimePulseTick = false;
|
||||
time_t userUTCTime = 0;
|
||||
timesource_t timeSource = _unsynced;
|
||||
|
||||
// container holding unique MAC address hashes with Memory Alloctor using PSRAM,
|
||||
@ -456,7 +455,7 @@ void setup() {
|
||||
|
||||
// initialize gps time
|
||||
#if (HAS_GPS)
|
||||
fetch_gpsTime();
|
||||
get_gpstime();
|
||||
#endif
|
||||
|
||||
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||
@ -464,7 +463,7 @@ void setup() {
|
||||
clock_init();
|
||||
#endif
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
#if (TIME_SYNC_LORASERVER) || (TIME_SYNC_LORAWAN)
|
||||
timesync_init(); // create loraserver time sync task
|
||||
#endif
|
||||
|
||||
|
14
src/ota.cpp
14
src/ota.cpp
@ -45,23 +45,15 @@ void start_ota_update() {
|
||||
// init display
|
||||
#ifdef HAS_DISPLAY
|
||||
|
||||
#ifndef DISPLAY_FLIP
|
||||
oledInit(OLED_128x64, false, false, -1, -1, MY_OLED_RST, 400000L);
|
||||
#else
|
||||
oledInit(OLED_128x64, true, false, -1, -1, MY_OLED_RST, 400000L);
|
||||
#endif
|
||||
setup_display();
|
||||
|
||||
// set display buffer
|
||||
oledSetBackBuffer(displaybuf);
|
||||
|
||||
oledFill(0, 1);
|
||||
dp_printf(0, 0, 0, 1, "SOFTWARE UPDATE");
|
||||
dp_printf(0, 1, 0, 0, "WiFi connect ..");
|
||||
dp_printf(0, 2, 0, 0, "Has Update? ..");
|
||||
dp_printf(0, 3, 0, 0, "Fetching ..");
|
||||
dp_printf(0, 4, 0, 0, "Downloading ..");
|
||||
dp_printf(0, 5, 0, 0, "Rebooting ..");
|
||||
oledDumpBuffer(displaybuf);
|
||||
dp_dump(displaybuf);
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Starting Wifi OTA update");
|
||||
@ -314,7 +306,7 @@ void ota_display(const uint8_t row, const std::string status,
|
||||
dp_printf(0, 7, 0, 0, " ");
|
||||
dp_printf(0, 7, 0, 0, msg.substr(0, 16).c_str());
|
||||
}
|
||||
oledDumpBuffer(displaybuf);
|
||||
dp_dump(displaybuf);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
// Payload send cycle and encoding
|
||||
@ -72,17 +72,15 @@
|
||||
#define OTA_MIN_BATT 3600 // minimum battery level for OTA [millivolt]
|
||||
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
|
||||
|
||||
// settings for syncing time of node with external time source
|
||||
#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]
|
||||
#define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 0]
|
||||
// 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_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
|
||||
|
||||
// settings for syncing time with timeserver applications
|
||||
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging
|
||||
#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_CYCLE 60 // delay between two time samples [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
|
||||
#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,
|
||||
// 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},
|
||||
{0x03, set_gps, 1, true}, {0x04, set_display, 1, true},
|
||||
{0x05, set_loradr, 1, true}, {0x06, set_lorapower, 1, true},
|
||||
|
@ -31,37 +31,35 @@ void calibrateTime(void) {
|
||||
time_t t = 0;
|
||||
uint16_t t_msec = 0;
|
||||
|
||||
#if (HAS_GPS)
|
||||
// fetch recent time from last NMEA record
|
||||
t = fetch_gpsTime(&t_msec);
|
||||
if (t) {
|
||||
timeSource = _gps;
|
||||
goto finish;
|
||||
}
|
||||
// kick off asychronous lora timesync if we have
|
||||
#if (HAS_LORA) && (TIME_SYNC_LORASERVER) || (TIME_SYNC_LORAWAN)
|
||||
timesync_request();
|
||||
#endif
|
||||
|
||||
// kick off asychronous Lora timeserver timesync if we have
|
||||
#if (HAS_LORA) && (TIME_SYNC_LORASERVER)
|
||||
send_timesync_req();
|
||||
// kick off asychronous lora network sync if we have
|
||||
#elif (HAS_LORA) && (TIME_SYNC_LORAWAN)
|
||||
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
|
||||
#endif
|
||||
// (only!) if we lost time, we try to fallback to local time source RTS or GPS
|
||||
if (timeSource == _unsynced) {
|
||||
|
||||
// no time from GPS -> fallback to RTC time while trying lora sync
|
||||
// has RTC -> fallback to RTC time
|
||||
#ifdef HAS_RTC
|
||||
t = get_rtctime();
|
||||
if (t) {
|
||||
t = get_rtctime();
|
||||
timeSource = _rtc;
|
||||
goto finish;
|
||||
}
|
||||
#endif
|
||||
|
||||
goto finish;
|
||||
// no RTC -> fallback to GPS time
|
||||
#if (HAS_GPS)
|
||||
t = get_gpstime(&t_msec);
|
||||
timeSource = _gps;
|
||||
#endif
|
||||
|
||||
finish:
|
||||
if (t)
|
||||
setMyTime((uint32_t)t, t_msec, timeSource); // set time
|
||||
|
||||
setMyTime((uint32_t)t, t_msec, timeSource); // set time
|
||||
} // fallback
|
||||
|
||||
else
|
||||
|
||||
// no fallback time source available -> we can't set time
|
||||
return;
|
||||
|
||||
} // calibrateTime()
|
||||
|
||||
@ -86,7 +84,7 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
|
||||
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);
|
||||
|
||||
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
|
||||
@ -107,11 +105,11 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
|
||||
timeSource = mytimesource; // set global variable
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c",
|
||||
millis() / 1000.0, timeSetSymbols[timeSource]);
|
||||
millis() / 1000.0, timeSetSymbols[mytimesource]);
|
||||
} else {
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c",
|
||||
millis() / 1000.0, timeSetSymbols[timeSource]);
|
||||
millis() / 1000.0, timeSetSymbols[mytimesource]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,7 +234,7 @@ void clock_init(void) {
|
||||
pinMode(HAS_DCF77, OUTPUT);
|
||||
#endif
|
||||
|
||||
userUTCTime = now();
|
||||
time_t userUTCTime = now();
|
||||
|
||||
xTaskCreatePinnedToCore(clock_loop, // task function
|
||||
"clockloop", // name of task
|
||||
|
343
src/timesync.cpp
343
src/timesync.cpp
@ -1,243 +1,274 @@
|
||||
/*
|
||||
|
||||
///--> IMPORTANT LICENSE NOTE for 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
|
||||
code of this file. The shown implementation example is covered by the
|
||||
repository's licencse, but you may not be eligible to deploy the applied
|
||||
algorithm in applications without granted license by the patent holder.
|
||||
code of this file for timesync option TIME_SYNC_LORASERVER. The shown
|
||||
implementation example is covered by the repository's licencse, but you may not
|
||||
be eligible to deploy the applied algorithm in applications without granted
|
||||
license by the patent holder.
|
||||
|
||||
You may use timesync option TIME_SYNC_LORAWAN if you do not want or cannot
|
||||
accept this.
|
||||
|
||||
*/
|
||||
|
||||
#if (TIME_SYNC_LORASERVER) && (HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
|
||||
#if (TIME_SYNC_LORASERVER) && (TIME_SYNC_LORAWAN)
|
||||
#error Duplicate timesync method selected. You must select either LORASERVER or LORAWAN timesync.
|
||||
#endif
|
||||
|
||||
#include "timesync.h"
|
||||
|
||||
// Local logging tag
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
typedef std::chrono::system_clock myClock;
|
||||
typedef myClock::time_point myClock_timepoint;
|
||||
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
|
||||
myClock_msecTick;
|
||||
|
||||
TaskHandle_t timeSyncReqTask = NULL;
|
||||
|
||||
static uint8_t time_sync_seqNo = (uint8_t)random(TIMEREQUEST_MAX_SEQNO);
|
||||
static bool timeSyncPending = false;
|
||||
static myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||
static myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||
static uint8_t time_sync_seqNo = (uint8_t)random(TIME_SYNC_MAX_SEQNO),
|
||||
sample_idx;
|
||||
static uint32_t timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps];
|
||||
static TaskHandle_t timeSyncProcTask;
|
||||
|
||||
// send time request message
|
||||
void send_timesync_req() {
|
||||
// create task for timeserver handshake processing, called from main.cpp
|
||||
void timesync_init() {
|
||||
xTaskCreatePinnedToCore(timesync_processReq, // task function
|
||||
"timesync_proc", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)1, // task parameter
|
||||
3, // priority of the task
|
||||
&timeSyncProcTask, // task handle
|
||||
1); // CPU core
|
||||
}
|
||||
|
||||
// if a timesync handshake is pending then exit
|
||||
// kickoff asnychronous timesync handshake
|
||||
void timesync_request(void) {
|
||||
// exit if a timesync handshake is already running
|
||||
if (timeSyncPending)
|
||||
return;
|
||||
// else unblock timesync task
|
||||
// start timesync handshake
|
||||
else {
|
||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
||||
xTaskNotifyGive(timeSyncReqTask);
|
||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request seqNo#%d started",
|
||||
millis() / 1000.0, time_sync_seqNo);
|
||||
xTaskNotifyGive(timeSyncProcTask); // unblock timesync task
|
||||
}
|
||||
}
|
||||
|
||||
// task for sending time sync requests
|
||||
void process_timesync_req(void *taskparameter) {
|
||||
// task for processing time sync request
|
||||
void IRAM_ATTR timesync_processReq(void *taskparameter) {
|
||||
|
||||
uint8_t k;
|
||||
uint16_t time_to_set_fraction_msec;
|
||||
uint32_t seq_no = 0, time_to_set;
|
||||
auto time_offset_ms = myClock_msecTick::zero();
|
||||
uint32_t rcv_seqNo = TIME_SYNC_END_FLAG, time_offset_ms;
|
||||
|
||||
// this task is an endless loop, waiting in blocked mode, until it is
|
||||
// unblocked by timesync_request(). It then waits to be notified from
|
||||
// timesync_serverAnswer(), which is called from LMIC each time a timestamp
|
||||
// from the timesource via LORAWAN arrived.
|
||||
|
||||
// --- asnychronous part: generate and collect timestamps from gateway ---
|
||||
|
||||
while (1) {
|
||||
|
||||
// reset all timestamps before next sync run
|
||||
time_offset_ms = myClock_msecTick::zero();
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++)
|
||||
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint();
|
||||
|
||||
// wait for kickoff
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
timeSyncPending = true;
|
||||
time_offset_ms = sample_idx = 0;
|
||||
|
||||
// wait until we are joined if we are not
|
||||
while (!LMIC.devaddr) {
|
||||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||||
}
|
||||
|
||||
// collect timestamp samples
|
||||
// collect timestamp samples in timestamp array
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||
// send sync request to server
|
||||
|
||||
// send timesync request
|
||||
#if (TIME_SYNC_LORASERVER) // aks user's timeserver (for LoRAWAN < 1.0.3)
|
||||
payload.reset();
|
||||
payload.addByte(time_sync_seqNo);
|
||||
SendPayload(TIMEPORT, prio_high);
|
||||
|
||||
// wait for a valid timestamp from recv_timesync_ans()
|
||||
while (seq_no != time_sync_seqNo) {
|
||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
|
||||
pdFALSE) {
|
||||
ESP_LOGW(TAG, "[%0.3f] Timesync handshake error: timeout",
|
||||
millis() / 1000.0);
|
||||
goto finish; // no valid sequence received before timeout
|
||||
}
|
||||
#elif (TIME_SYNC_LORAWAN) // ask network (requires LoRAWAN >= 1.0.3)
|
||||
LMIC_requestNetworkTime(timesync_serverAnswer, &time_sync_seqNo);
|
||||
// trigger send to immediately get DevTimeAns on class A device
|
||||
LMIC_sendAlive();
|
||||
#endif
|
||||
// wait until a timestamp was received
|
||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &rcv_seqNo,
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) {
|
||||
ESP_LOGW(TAG, "[%0.3f] Timesync aborted: timed out", millis() / 1000.0);
|
||||
goto Fail; // no timestamp received before timeout
|
||||
}
|
||||
|
||||
// process answer
|
||||
k = seq_no % TIME_SYNC_SAMPLES;
|
||||
// check if we are in handshake with server
|
||||
if (rcv_seqNo != time_sync_seqNo) {
|
||||
ESP_LOGW(TAG, "[%0.3f] Timesync aborted: handshake out of sync",
|
||||
millis() / 1000.0);
|
||||
goto Fail;
|
||||
}
|
||||
|
||||
// calculate time diff from collected timestamps
|
||||
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
|
||||
time_point_cast<milliseconds>(time_sync_tx[k]);
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// calculate time diff with received timestamp
|
||||
time_offset_ms += timesync_timestamp[sample_idx][timesync_rx] -
|
||||
timesync_timestamp[sample_idx][timesync_tx];
|
||||
#endif
|
||||
|
||||
// wrap around seqNo, keeping it in time port range
|
||||
time_sync_seqNo++;
|
||||
if (time_sync_seqNo > TIMEREQUEST_MAX_SEQNO) {
|
||||
// increment sample_idx and time_sync_seqNo, keeping it in range
|
||||
if (++time_sync_seqNo > TIME_SYNC_MAX_SEQNO)
|
||||
time_sync_seqNo = 0;
|
||||
}
|
||||
sample_idx++;
|
||||
|
||||
if (i < TIME_SYNC_SAMPLES - 1) {
|
||||
// wait until next cycle
|
||||
// if we are not in last cycle, pause until next cycle
|
||||
if (i < TIME_SYNC_SAMPLES - 1)
|
||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||
} else { // before sending last time sample...
|
||||
// ...send flush to open a receive window for last time_sync_answer
|
||||
payload.reset();
|
||||
payload.addByte(0x99);
|
||||
SendPayload(RCMDPORT, prio_high);
|
||||
// ...send a alive open a receive window for last time_sync_answer
|
||||
LMIC_sendAlive();
|
||||
}
|
||||
} // end of for loop to collect timestamp samples
|
||||
|
||||
} // for i
|
||||
|
||||
// --- time critial part: evaluate timestamps and calculate time ---
|
||||
|
||||
// mask application irq to ensure accurate timing
|
||||
mask_user_IRQ();
|
||||
|
||||
// average time offset over all collected diffs
|
||||
// calculate average time offset over the summed up difference
|
||||
// add msec from latest gateway time, and apply a compensation constant for
|
||||
// processing times on node and gateway
|
||||
time_offset_ms /= TIME_SYNC_SAMPLES;
|
||||
|
||||
// calculate time offset with millisecond precision using LMIC's time base,
|
||||
// since we use LMIC's ostime_t txEnd as tx timestamp.
|
||||
// Also apply calibration const to compensate processing time.
|
||||
time_offset_ms +=
|
||||
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
|
||||
TIME_SYNC_FIXUP + timesync_timestamp[sample_idx - 1][gwtime_msec];
|
||||
|
||||
// calculate absolute time in UTC epoch: convert to whole seconds, round to
|
||||
// ceil, and calculate fraction milliseconds
|
||||
time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1;
|
||||
// calculate fraction milliseconds
|
||||
time_to_set_fraction_msec = (uint16_t)(time_offset_ms.count() % 1000);
|
||||
// calculate absolute UTC time: take latest timestamp received from
|
||||
// gateway, convert to whole seconds, round to ceil, add fraction seconds
|
||||
setMyTime(timesync_timestamp[sample_idx - 1][gwtime_sec] +
|
||||
time_offset_ms / 1000,
|
||||
time_offset_ms % 1000, _lora);
|
||||
|
||||
setMyTime(time_to_set, time_to_set_fraction_msec, _lora);
|
||||
// send timesync end char to show timesync was successful
|
||||
payload.reset();
|
||||
payload.addByte(TIME_SYNC_END_FLAG);
|
||||
SendPayload(RCMDPORT, prio_high);
|
||||
goto Finish;
|
||||
|
||||
finish:
|
||||
Fail:
|
||||
// set retry timer
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
|
||||
|
||||
Finish:
|
||||
// end of time critical section: release app irq lock
|
||||
timeSyncPending = false;
|
||||
unmask_user_IRQ();
|
||||
|
||||
} // infinite while(1)
|
||||
}
|
||||
|
||||
// called from lorawan.cpp after time_sync_req was sent
|
||||
void store_time_sync_req(uint32_t timestamp) {
|
||||
// store incoming timestamps
|
||||
void timesync_store(uint32_t timestamp, timesync_t timestamp_type) {
|
||||
ESP_LOGD(TAG, "[%0.3f] seq#%d[%d]: timestamp(t%d)=%d", millis() / 1000.0,
|
||||
time_sync_seqNo, sample_idx, timestamp_type, timestamp);
|
||||
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
|
||||
}
|
||||
|
||||
// callback function to receive time answer from network or answer
|
||||
void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag) {
|
||||
|
||||
// if no timesync handshake is pending then exit
|
||||
if (!timeSyncPending)
|
||||
return;
|
||||
|
||||
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
||||
time_sync_tx[k] += milliseconds(timestamp);
|
||||
// mask application irq to ensure accurate timing
|
||||
mask_user_IRQ();
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d of %d sent at %d.%03d",
|
||||
millis() / 1000.0, k + 1, TIME_SYNC_SAMPLES, timestamp / 1000,
|
||||
timestamp % 1000);
|
||||
}
|
||||
// store LMIC time when we received the timesync answer
|
||||
ostime_t rxTime = osticks2ms(os_getTime());
|
||||
|
||||
// process timeserver timestamp answer, called by myRxCallback() in lorawan.cpp
|
||||
int recv_timesync_ans(const uint8_t buf[], const uint8_t buf_len) {
|
||||
int rc = 0;
|
||||
uint8_t rcv_seqNo = *(uint8_t *)pUserData;
|
||||
uint16_t timestamp_msec = 0;
|
||||
uint32_t timestamp_sec = 0;
|
||||
|
||||
/*
|
||||
parse 7 byte timesync_answer:
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
|
||||
byte meaning
|
||||
1 sequence number (taken from node's time_sync_req)
|
||||
2 timezone in 15 minutes steps
|
||||
3..6 current second (from epoch time 1970)
|
||||
7 1/250ths fractions of current second
|
||||
*/
|
||||
// pUserData: contains pointer to payload buffer
|
||||
// flag: length of buffer
|
||||
|
||||
// if no timesync handshake is pending then exit
|
||||
if (!timeSyncPending)
|
||||
return 0; // failure
|
||||
// Store the instant the time request of the node was received on the gateway
|
||||
timesync_store(rxTime, timesync_rx);
|
||||
|
||||
// extract 1 byte timerequest sequence number from buffer
|
||||
uint8_t seq_no = buf[0];
|
||||
buf++;
|
||||
// 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 (buf_len != TIME_SYNC_FRAME_LENGTH) {
|
||||
if (seq_no == 0xff)
|
||||
if (flag != TIME_SYNC_FRAME_LENGTH) {
|
||||
if (rcv_seqNo == TIME_SYNC_END_FLAG)
|
||||
ESP_LOGI(TAG, "[%0.3f] Timeserver error: no confident time available",
|
||||
millis() / 1000.0);
|
||||
else
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: spurious data received",
|
||||
millis() / 1000.0);
|
||||
return 0; // failure
|
||||
goto Exit; // failure
|
||||
}
|
||||
|
||||
else { // we received a probably valid time frame
|
||||
goto Finish;
|
||||
|
||||
uint8_t k = seq_no % TIME_SYNC_SAMPLES;
|
||||
#elif (TIME_SYNC_LORAWAN)
|
||||
|
||||
// pointers to 4 bytes containing UTC seconds since unix epoch, msb
|
||||
uint32_t timestamp_sec, *timestamp_ptr;
|
||||
// pUserData: contains pointer to SeqNo
|
||||
// flag: indicates if we got a recent time from the network
|
||||
|
||||
// extract 1 byte timezone from buffer (one step being 15min * 60s = 900s)
|
||||
// uint32_t timezone_sec = buf[0] * 900; // for future use
|
||||
buf++;
|
||||
|
||||
// extract 4 bytes timestamp from buffer
|
||||
// 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 platform dependent
|
||||
timestamp_sec = __builtin_bswap32(*timestamp_ptr);
|
||||
buf += 4;
|
||||
// extract 1 byte fractional seconds in 2^-8 second steps
|
||||
// (= 1/250th sec), we convert this to ms
|
||||
uint16_t timestamp_msec = 4 * buf[0];
|
||||
|
||||
// construct the timepoint when message was seen on gateway
|
||||
time_sync_rx[k] +=
|
||||
seconds(timestamp_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]))) {
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d of %d rcvd at %d.%03d",
|
||||
millis() / 1000.0, k + 1, TIME_SYNC_SAMPLES, timestamp_sec,
|
||||
timestamp_msec);
|
||||
|
||||
// inform processing task
|
||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||
|
||||
return 1; // success
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: outdated time received",
|
||||
millis() / 1000.0);
|
||||
return 0; // failure
|
||||
}
|
||||
if (flag != 1) {
|
||||
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.
|
||||
// It contains the following fields:
|
||||
// - tLocal: the value returned by os_GetTime() when the time
|
||||
// request was sent to the gateway, and
|
||||
// - tNetwork: the seconds between the GPS epoch and the time
|
||||
// the gateway received the time request
|
||||
lmic_time_reference_t lmicTime;
|
||||
|
||||
// Populate lmic_time_reference
|
||||
if ((LMIC_getNetworkTimeReference(&lmicTime)) != 1) {
|
||||
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 received on the gateway and the
|
||||
// current time on the node
|
||||
timestamp_msec = rxTime - lmicTime.tLocal;
|
||||
goto Finish;
|
||||
|
||||
#endif // (TIME_SYNC_LORAWAN)
|
||||
|
||||
Finish:
|
||||
// check if calculated time is recent
|
||||
if (timeIsValid(timestamp_sec)) {
|
||||
// store time received from gateway
|
||||
timesync_store(timestamp_sec, gwtime_sec);
|
||||
timesync_store(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 ? rcv_seqNo : TIME_SYNC_END_FLAG),
|
||||
eSetBits);
|
||||
}
|
||||
|
||||
// create task for timeserver handshake processing, called from main.cpp
|
||||
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
|
||||
#endif // HAS_LORA
|
Loading…
Reference in New Issue
Block a user