Merge branch 'development' into bme280
This commit is contained in:
commit
3cc38abaea
2
.gitignore
vendored
2
.gitignore
vendored
@ -11,5 +11,3 @@
|
||||
.gcc-flags.json
|
||||
src/loraconf.h
|
||||
src/ota.conf
|
||||
src/DBtimesync.cpp
|
||||
include/DBtimesync.h
|
@ -86,7 +86,7 @@ If your device has a fixed DEVEUI enter this in your local loraconf.h file. Duri
|
||||
|
||||
If your device has silicon **Unique ID** which is stored in serial EEPROM Microchip 24AA02E64 you don't need to change anything. The Unique ID will be read during startup and DEVEUI will be generated from it, overriding settings in loraconf.h.
|
||||
|
||||
If your device has a **real time clock** it can be updated bei either LoRaWAN network or GPS time, according to settings *TIME_SYNC_INTERVAL* and *TIME_SYNC_LORA* in paxcounter.conf.
|
||||
If your device has a **real time clock** it can be updated bei either LoRaWAN network or GPS time, according to settings *TIME_SYNC_INTERVAL* and *TIME_SYNC_LORAWAN* in paxcounter.conf.
|
||||
|
||||
# Building
|
||||
|
||||
@ -366,6 +366,10 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts.
|
||||
|
||||
Device answers with it's local time/date (UTC Unix epoch) on Port 9.
|
||||
|
||||
0x87 set time/date
|
||||
|
||||
Device synchronizes it's time/date by calling the preconfigured time source.
|
||||
|
||||
|
||||
# License
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
#include <Arduino.h>
|
||||
|
||||
// Time functions
|
||||
#include <Time.h>
|
||||
#include "microTime.h"
|
||||
#include <Timezone.h>
|
||||
#include <RtcDateTime.h>
|
||||
#include <Ticker.h>
|
||||
|
@ -4,8 +4,8 @@
|
||||
#include "globals.h"
|
||||
#include "rcommand.h"
|
||||
#include "timekeeper.h"
|
||||
#if(DBTIMESYNC)
|
||||
#include "DBtimesync.h"
|
||||
#if(TIME_SYNC_TIMESERVER)
|
||||
#include "timesync.h"
|
||||
#endif
|
||||
|
||||
// LMIC-Arduino LoRaWAN Stack
|
||||
|
@ -20,7 +20,8 @@
|
||||
|
||||
#endif
|
||||
|
||||
// MyDevices CayenneLPP 2.0 types for Packed Sensor Payload, not using channels, but different FPorts
|
||||
// MyDevices CayenneLPP 2.0 types for Packed Sensor Payload, not using channels,
|
||||
// but different FPorts
|
||||
#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01m
|
||||
#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed MSB
|
||||
#define LPP_DIGITAL_INPUT 0 // 1 byte
|
||||
@ -40,6 +41,7 @@ public:
|
||||
void reset(void);
|
||||
uint8_t getSize(void);
|
||||
uint8_t *getBuffer(void);
|
||||
void addByte(uint8_t value);
|
||||
void addCount(uint16_t value, uint8_t sniffytpe);
|
||||
void addConfig(configData_t value);
|
||||
void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem,
|
||||
@ -72,7 +74,7 @@ private:
|
||||
void writeFloat(float value);
|
||||
void writeUFloat(float value);
|
||||
void writePressure(float value);
|
||||
void writeVersion(char * version);
|
||||
void writeVersion(char *version);
|
||||
void writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f, bool g,
|
||||
bool h);
|
||||
|
||||
|
@ -9,8 +9,8 @@
|
||||
#include <rom/rtc.h>
|
||||
#include "cyclic.h"
|
||||
#include "timekeeper.h"
|
||||
#if(DBTIMESYNC)
|
||||
#include "DBtimesync.h"
|
||||
#if(TIME_SYNC_TIMESERVER)
|
||||
#include "timesync.h"
|
||||
#endif
|
||||
|
||||
// table of remote commands and assigned functions
|
||||
|
20
include/timesync.h
Normal file
20
include/timesync.h
Normal file
@ -0,0 +1,20 @@
|
||||
#ifndef _TIME_SYNC_TIMESERVER_H
|
||||
#define _TIME_SYNC_TIMESERVER_H
|
||||
|
||||
#include <chrono>
|
||||
#include "globals.h"
|
||||
#include "timesync.h"
|
||||
#include "timekeeper.h"
|
||||
|
||||
#define TIME_SYNC_SAMPLES 3 // number of time requests for averaging
|
||||
#define TIME_SYNC_CYCLE 20 // seconds between two time requests
|
||||
#define TIME_SYNC_TIMEOUT 120 // timeout seconds waiting for timeserver answer
|
||||
#define TIME_SYNC_TRIGGER 1 // time deviation threshold triggering time sync
|
||||
#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length
|
||||
|
||||
void send_timesync_req(void);
|
||||
void recv_timesync_ans(uint8_t buf[], uint8_t buf_len);
|
||||
void process_timesync_req(void *taskparameter);
|
||||
void store_time_sync_req(time_t secs, uint32_t micros);
|
||||
|
||||
#endif
|
@ -30,10 +30,10 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
|
||||
|
||||
[common]
|
||||
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
||||
release_version = 1.7.36
|
||||
release_version = 1.7.38
|
||||
; 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
|
||||
debug_level = 4
|
||||
; UPLOAD MODE: select esptool to flash via USB/UART, select custom to upload to cloud for OTA
|
||||
upload_protocol = esptool
|
||||
;upload_protocol = custom
|
||||
@ -55,7 +55,6 @@ lib_deps_rtc =
|
||||
RTC@^2.3.0
|
||||
lib_deps_basic =
|
||||
ArduinoJson@^5.13.1
|
||||
Time@>=1.5
|
||||
Timezone@^1.2.2
|
||||
lib_deps_all =
|
||||
${common.lib_deps_basic}
|
||||
|
249
src/TTN/Nodered-Timeserver.json
Normal file
249
src/TTN/Nodered-Timeserver.json
Normal file
@ -0,0 +1,249 @@
|
||||
[
|
||||
{
|
||||
"id": "49e3c067.e782e",
|
||||
"type": "change",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Payload",
|
||||
"rules": [
|
||||
{
|
||||
"t": "change",
|
||||
"p": "topic",
|
||||
"pt": "msg",
|
||||
"from": "up",
|
||||
"fromt": "str",
|
||||
"to": "down",
|
||||
"tot": "str"
|
||||
},
|
||||
{
|
||||
"t": "move",
|
||||
"p": "payload",
|
||||
"pt": "msg",
|
||||
"to": "payload.payload_raw",
|
||||
"tot": "msg"
|
||||
},
|
||||
{
|
||||
"t": "set",
|
||||
"p": "payload.port",
|
||||
"pt": "msg",
|
||||
"to": "9",
|
||||
"tot": "num"
|
||||
},
|
||||
{
|
||||
"t": "set",
|
||||
"p": "payload.confirmed",
|
||||
"pt": "msg",
|
||||
"to": "false",
|
||||
"tot": "bool"
|
||||
},
|
||||
{
|
||||
"t": "set",
|
||||
"p": "payload.schedule",
|
||||
"pt": "msg",
|
||||
"to": "replace",
|
||||
"tot": "str"
|
||||
}
|
||||
],
|
||||
"action": "",
|
||||
"property": "",
|
||||
"from": "",
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 220,
|
||||
"y": 360,
|
||||
"wires": [
|
||||
[
|
||||
"84f1cda2.069e7"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "cc140589.dea168",
|
||||
"type": "mqtt in",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "listen",
|
||||
"topic": "+/devices/+/up",
|
||||
"qos": "2",
|
||||
"broker": "2a15ab6f.ab2244",
|
||||
"x": 70,
|
||||
"y": 120,
|
||||
"wires": [
|
||||
[
|
||||
"9f4b8dd3.2f0d2"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "72d5e7ee.d1eba8",
|
||||
"type": "mqtt out",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "send",
|
||||
"topic": "",
|
||||
"qos": "",
|
||||
"retain": "",
|
||||
"broker": "2a15ab6f.ab2244",
|
||||
"x": 690,
|
||||
"y": 360,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "4f97d75.6c87528",
|
||||
"type": "json",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Convert",
|
||||
"property": "payload",
|
||||
"action": "",
|
||||
"pretty": false,
|
||||
"x": 220,
|
||||
"y": 200,
|
||||
"wires": [
|
||||
[
|
||||
"8ed813a9.a9319"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "9f4b8dd3.2f0d2",
|
||||
"type": "switch",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Timeport",
|
||||
"property": "payload",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "cont",
|
||||
"v": "\"port\":9",
|
||||
"vt": "str"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 220,
|
||||
"y": 120,
|
||||
"wires": [
|
||||
[
|
||||
"4f97d75.6c87528"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "f4c5b6de.f95148",
|
||||
"type": "function",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Time_Sync_Ans",
|
||||
"func": "/* LoRaWAN Timeserver\n\nconstruct 6 byte timesync_answer from gateway timestamp and node's time_sync_req\n\nbyte meaning\n0 sequence number (taken from node's time_sync_req)\n1..4 current second (from epoch time 1970)\n5 1/250ths fractions of current second\n\n*/\n\n let buf = new ArrayBuffer(6);\n let timestamp = (+new Date(msg.payload.metadata.gateways[0].time));\n \n var seconds = Math.floor(timestamp/1000);\n var fractions = (timestamp % 1000) / 4;\n var seqno = msg.payload.payload_raw[0];\n\n new DataView(buf).setUint8(0, seqno);\n new DataView(buf).setUint32(1, seconds);\n new DataView(buf).setUint8(5, fractions);\n\n msg.payload = new Buffer(new Uint8Array(buf));\n \n return msg;",
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"x": 400,
|
||||
"y": 280,
|
||||
"wires": [
|
||||
[
|
||||
"49e3c067.e782e"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dac8aafa.389298",
|
||||
"type": "json",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Convert",
|
||||
"property": "payload",
|
||||
"action": "",
|
||||
"pretty": false,
|
||||
"x": 540,
|
||||
"y": 360,
|
||||
"wires": [
|
||||
[
|
||||
"72d5e7ee.d1eba8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "8ed813a9.a9319",
|
||||
"type": "base64",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Decode",
|
||||
"action": "",
|
||||
"property": "payload.payload_raw",
|
||||
"x": 380,
|
||||
"y": 200,
|
||||
"wires": [
|
||||
[
|
||||
"f868bce2.dde67"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "84f1cda2.069e7",
|
||||
"type": "base64",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Encode",
|
||||
"action": "",
|
||||
"property": "payload.payload_raw",
|
||||
"x": 380,
|
||||
"y": 360,
|
||||
"wires": [
|
||||
[
|
||||
"dac8aafa.389298"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "6190967b.01f758",
|
||||
"type": "comment",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "LoRaWAN Timeserver",
|
||||
"info": "PLEASE NOTE: There is a patent filed for the time sync algorithm used in the\ncode of this file. The shown implementation example is covered by the\nrepository's licencse, but you may not be eligible to deploy the applied\nalgorithm in applications without granted license by the patent holder.",
|
||||
"x": 120,
|
||||
"y": 40,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "f868bce2.dde67",
|
||||
"type": "switch",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Timechecker",
|
||||
"property": "payload.metadata.gateways[0].time",
|
||||
"propertyType": "msg",
|
||||
"rules": [
|
||||
{
|
||||
"t": "lte",
|
||||
"v": "payload.metadata.time",
|
||||
"vt": "msg"
|
||||
}
|
||||
],
|
||||
"checkall": "true",
|
||||
"repair": false,
|
||||
"outputs": 1,
|
||||
"x": 550,
|
||||
"y": 200,
|
||||
"wires": [
|
||||
[
|
||||
"f4c5b6de.f95148"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "2a15ab6f.ab2244",
|
||||
"type": "mqtt-broker",
|
||||
"z": "",
|
||||
"name": "eu.thethings.network:1883",
|
||||
"broker": "eu.thethings.network",
|
||||
"port": "1883",
|
||||
"tls": "",
|
||||
"clientid": "",
|
||||
"usetls": false,
|
||||
"compatmode": true,
|
||||
"keepalive": "60",
|
||||
"cleansession": true,
|
||||
"birthTopic": "",
|
||||
"birthQos": "0",
|
||||
"birthPayload": "",
|
||||
"closeTopic": "",
|
||||
"closeQos": "0",
|
||||
"closePayload": "",
|
||||
"willTopic": "",
|
||||
"willQos": "0",
|
||||
"willPayload": ""
|
||||
}
|
||||
]
|
@ -63,6 +63,14 @@ function Decoder(bytes, port) {
|
||||
return decode(bytes, [uint16], ['voltage']);
|
||||
}
|
||||
|
||||
if (port === 9) {
|
||||
// timesync request
|
||||
if (bytes.length === 1) {
|
||||
decoded.timesync_seqno = bytes[0];
|
||||
}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -67,6 +67,12 @@ function Decoder(bytes, port) {
|
||||
decoded.battery = (bytes[i++] << 8) | bytes[i++];}
|
||||
}
|
||||
|
||||
if (port === 9) {
|
||||
if (bytes.length === 1) {
|
||||
decoded.timesync_seqno = bytes[0];
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// Hardware related definitions for Heltec V2 LoRa-32 Board
|
||||
// Hardware related definitions for Heltec V1 LoRa-32 Board
|
||||
|
||||
//#define HAS_BME 1 // Enable BME sensors in general
|
||||
//#define HAS_BME680 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL
|
||||
@ -19,13 +19,13 @@
|
||||
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
#define MY_OLED_SDA (21)
|
||||
#define MY_OLED_SCL (22)
|
||||
#define MY_OLED_SDA (4)
|
||||
#define MY_OLED_SCL (15)
|
||||
#define MY_OLED_RST (16)
|
||||
|
||||
// Pins for LORA chip SPI interface come from board file, we need some
|
||||
// additional definitions for LMIC
|
||||
#define LORA_IO1 (33)
|
||||
#define LORA_IO2 LMIC_UNUSED_PIN
|
||||
#define LORA_IO2 (32)
|
||||
|
||||
#endif
|
@ -18,6 +18,10 @@
|
||||
#define HAS_LED LED_BUILTIN // white LED on board
|
||||
#define HAS_BUTTON KEY_BUILTIN // button "PROG" on board
|
||||
|
||||
//#define HAS_BATTERY_PROBE ADC2_GPIO13_CHANNEL // battery probe GPIO pin
|
||||
#define BATT_FACTOR 4 // voltage divider 220k/100k on board
|
||||
#define HAS_LOWPOWER_SWITCH GPIO_NUM_21 // switches battery power
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
#define MY_OLED_SDA (4)
|
||||
#define MY_OLED_SCL (15)
|
||||
|
@ -33,10 +33,13 @@ void irqHandler(void *pvParameters) {
|
||||
if (InterruptStatus & CYCLIC_IRQ)
|
||||
doHousekeeping();
|
||||
|
||||
#ifdef TIME_SYNC_INTERVAL
|
||||
#if (TIME_SYNC_INTERVAL)
|
||||
// is time to be synced?
|
||||
if (InterruptStatus & TIMESYNC_IRQ)
|
||||
setTime(timeProvider());
|
||||
if (InterruptStatus & TIMESYNC_IRQ) {
|
||||
time_t t = timeProvider();
|
||||
if (timeIsValid(t))
|
||||
setTime(t);
|
||||
}
|
||||
#endif
|
||||
|
||||
// is time to send the payload?
|
||||
|
@ -155,7 +155,7 @@ void get_hard_deveui(uint8_t *pdeveui) {
|
||||
#endif // MCP 24AA02E64
|
||||
}
|
||||
|
||||
#if(VERBOSE)
|
||||
#if (VERBOSE)
|
||||
|
||||
// Display OTAA keys
|
||||
void showLoraKeys(void) {
|
||||
@ -175,6 +175,8 @@ void showLoraKeys(void) {
|
||||
|
||||
void onEvent(ev_t ev) {
|
||||
char buff[24] = "";
|
||||
uint32_t now_micros = 0;
|
||||
|
||||
switch (ev) {
|
||||
|
||||
case EV_SCAN_TIMEOUT:
|
||||
@ -225,24 +227,38 @@ void onEvent(ev_t ev) {
|
||||
|
||||
case EV_TXCOMPLETE:
|
||||
|
||||
#if(DBTIMESYNC)
|
||||
if (!(LMIC.txrxFlags & TXRX_ACK) && time_sync_seqNo)
|
||||
time_sync_messages[time_sync_seqNo - 1] = LMIC.txend;
|
||||
#if (TIME_SYNC_TIMESERVER)
|
||||
// if last packet sent was a timesync request, store TX timestamp
|
||||
if (LMIC.pendTxPort == TIMEPORT)
|
||||
store_time_sync_req(now(now_micros), now_micros);
|
||||
// maybe use more precise osticks2ms(LMIC.txend) here?
|
||||
#endif
|
||||
|
||||
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED_ACK")
|
||||
: PSTR("TX_COMPLETE"));
|
||||
sprintf(display_line6, " "); // clear previous lmic status
|
||||
|
||||
if (LMIC.dataLen) {
|
||||
ESP_LOGI(TAG, "Received %d bytes of payload, RSSI -%d SNR %d",
|
||||
if (LMIC.dataLen) { // did we receive data -> display info
|
||||
ESP_LOGI(TAG, "Received %d bytes of payload, RSSI %d SNR %d",
|
||||
LMIC.dataLen, LMIC.rssi, LMIC.snr / 4);
|
||||
sprintf(display_line6, "RSSI -%d SNR %d", LMIC.rssi, LMIC.snr / 4);
|
||||
sprintf(display_line6, "RSSI %d SNR %d", LMIC.rssi, LMIC.snr / 4);
|
||||
|
||||
// check if command is received on command port, then call interpreter
|
||||
if ((LMIC.txrxFlags & TXRX_PORT) &&
|
||||
(LMIC.frame[LMIC.dataBeg - 1] == RCMDPORT))
|
||||
if (LMIC.txrxFlags & TXRX_PORT) { // FPort -> use to switch
|
||||
switch (LMIC.frame[LMIC.dataBeg - 1]) {
|
||||
#if (TIME_SYNC_TIMESERVER)
|
||||
case TIMEPORT: // timesync answer -> call timesync processor
|
||||
recv_timesync_ans(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
|
||||
break;
|
||||
#endif
|
||||
case RCMDPORT: // opcode -> call rcommand interpreter
|
||||
rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
|
||||
break;
|
||||
default: // unknown port -> display info
|
||||
ESP_LOGI(TAG, "Received data on unsupported port #%d",
|
||||
LMIC.frame[LMIC.dataBeg - 1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@ -385,15 +401,15 @@ esp_err_t lora_stack_init() {
|
||||
// in src/lmic_config.h if you are limited on battery.
|
||||
LMIC_setClockError(MAX_CLOCK_ERROR * CLOCK_ERROR_PROCENTAGE / 100);
|
||||
// Set the data rate to Spreading Factor 7. This is the fastest supported
|
||||
// rate for 125 kHz channels, and it minimizes air time and battery power. Set
|
||||
// the transmission power to 14 dBi (25 mW).
|
||||
// rate for 125 kHz channels, and it minimizes air time and battery power.
|
||||
// Set the transmission power to 14 dBi (25 mW).
|
||||
LMIC_setDrTxpow(DR_SF7, 14);
|
||||
|
||||
#if defined(CFG_US915) || defined(CFG_au921)
|
||||
// in the US, with TTN, it saves join time if we start on subband 1 (channels
|
||||
// 8-15). This will get overridden after the join by parameters from the
|
||||
// network. If working with other networks or in other regions, this will need
|
||||
// to be changed.
|
||||
// in the US, with TTN, it saves join time if we start on subband 1
|
||||
// (channels 8-15). This will get overridden after the join by parameters
|
||||
// from the network. If working with other networks or in other regions,
|
||||
// this will need to be changed.
|
||||
LMIC_selectSubBand(1);
|
||||
#endif
|
||||
|
||||
@ -417,11 +433,8 @@ void lora_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
||||
ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0);
|
||||
break;
|
||||
}
|
||||
if (ret == pdTRUE) {
|
||||
ESP_LOGI(TAG, "%d bytes enqueued for LORA interface", message->MessageSize);
|
||||
} else {
|
||||
if (ret != pdTRUE)
|
||||
ESP_LOGW(TAG, "LORA sendqueue is full");
|
||||
}
|
||||
}
|
||||
|
||||
void lora_queuereset(void) { xQueueReset(LoraSendQueue); }
|
||||
@ -457,8 +470,7 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
}
|
||||
|
||||
// Update userUTCTime, considering the difference between the GPS and UTC
|
||||
// time, and the leap seconds
|
||||
// !!! DANGER !!! This code will expire in next year with leap second
|
||||
// time, and the leap seconds until year 2019
|
||||
*pUserUTCTime = lmicTimeReference.tNetwork + 315964800;
|
||||
// Current time, in ticks
|
||||
ostime_t ticksNow = os_getTime();
|
||||
|
@ -415,7 +415,7 @@ void setup() {
|
||||
#endif
|
||||
#endif // HAS_BUTTON
|
||||
|
||||
#ifdef TIME_SYNC_INTERVAL
|
||||
#if(TIME_SYNC_INTERVAL)
|
||||
// start pps timepulse
|
||||
ESP_LOGI(TAG, "Starting Timekeeper...");
|
||||
assert(timepulse_init()); // setup timepulse
|
||||
@ -425,7 +425,7 @@ void setup() {
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482 || defined HAS_DCF77
|
||||
#ifndef TIME_SYNC_INTERVAL
|
||||
#if (!TIME_SYNC_INTERVAL)
|
||||
#error for clock controller function TIME_SNYC_INTERVAL must be defined in paxcounter.conf
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Starting Clock Controller...");
|
||||
|
@ -66,9 +66,9 @@
|
||||
#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 2 // sync time attempt each .. minutes from time source (GPS/LORA/RTC) [default = 60], comment out means off
|
||||
#define TIME_SYNC_LORA 0 // set to 1 to use LORA network as time source, comment out means off [default = off]
|
||||
#define DBTIMESYNC 0 // set to 1 to use DB LORA timeserver with patented sync algorithm [default = off]
|
||||
#define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source (GPS/LORA/RTC) [default = 60], 0 means off
|
||||
#define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 0]
|
||||
#define TIME_SYNC_TIMESERVER 1 // set to 1 to use LORA timeserver as time source, 0 means off [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
|
||||
|
@ -18,6 +18,8 @@ uint8_t *PayloadConvert::getBuffer(void) { return buffer; }
|
||||
|
||||
#if PAYLOAD_ENCODER == 1
|
||||
|
||||
void PayloadConvert::addByte(uint8_t value) { buffer[cursor++] = (value); }
|
||||
|
||||
void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) {
|
||||
buffer[cursor++] = highByte(value);
|
||||
buffer[cursor++] = lowByte(value);
|
||||
@ -141,6 +143,8 @@ void PayloadConvert::addTime(time_t value) {
|
||||
|
||||
#elif PAYLOAD_ENCODER == 2
|
||||
|
||||
void PayloadConvert::addByte(uint8_t value) { writeUint8(value); }
|
||||
|
||||
void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) {
|
||||
writeUint16(value);
|
||||
}
|
||||
@ -299,6 +303,11 @@ void PayloadConvert::writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f,
|
||||
|
||||
#elif (PAYLOAD_ENCODER == 3 || PAYLOAD_ENCODER == 4)
|
||||
|
||||
void PayloadConvert::addByte(uint8_t value) {
|
||||
/*
|
||||
not implemented
|
||||
*/ }
|
||||
|
||||
void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) {
|
||||
switch (snifftype) {
|
||||
case MAC_SNIFF_WIFI:
|
||||
|
@ -38,7 +38,7 @@ void set_reset(uint8_t val[]) {
|
||||
break;
|
||||
case 9: // reset and ask for software update via Wifi OTA
|
||||
ESP_LOGI(TAG, "Remote command: software update via Wifi");
|
||||
#if(USE_OTA)
|
||||
#if (USE_OTA)
|
||||
sprintf(display_line6, "Software update");
|
||||
cfg.runmode = 1;
|
||||
#else
|
||||
@ -278,40 +278,35 @@ void get_time(uint8_t val[]) {
|
||||
SendPayload(TIMEPORT, prio_high);
|
||||
};
|
||||
|
||||
void set_time(uint8_t val[]) {
|
||||
ESP_LOGI(TAG, "Timesync requested by timeserver");
|
||||
timeSync();
|
||||
};
|
||||
|
||||
void set_flush(uint8_t val[]) {
|
||||
ESP_LOGI(TAG, "Remote command: flush");
|
||||
// does nothing
|
||||
// used to open receive window on LoRaWAN class a nodes
|
||||
};
|
||||
|
||||
// assign previously defined functions to set of numeric remote commands
|
||||
// format: opcode, function, #bytes params,
|
||||
// flag (true = do make settings persistent / false = don't)
|
||||
//
|
||||
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_lorasf, 1, true},
|
||||
{0x06, set_lorapower, 1, true},
|
||||
{0x07, set_loraadr, 1, true},
|
||||
{0x08, set_screensaver, 1, true},
|
||||
{0x09, set_reset, 1, true},
|
||||
{0x0a, set_sendcycle, 1, true},
|
||||
{0x0b, set_wifichancycle, 1, true},
|
||||
{0x0c, set_blescantime, 1, true},
|
||||
{0x0d, set_vendorfilter, 1, false},
|
||||
{0x0e, set_blescan, 1, true},
|
||||
{0x0f, set_wifiant, 1, true},
|
||||
{0x10, set_rgblum, 1, true},
|
||||
{0x11, set_monitor, 1, true},
|
||||
{0x12, set_beacon, 7, false},
|
||||
{0x13, set_sensor, 2, true},
|
||||
{0x80, get_config, 0, false},
|
||||
{0x81, get_status, 0, false},
|
||||
{0x84, get_gps, 0, false},
|
||||
{0x85, get_bme, 0, false},
|
||||
{0x86, get_time, 0, false}
|
||||
#if(DBTIMESYNC)
|
||||
,
|
||||
{TIME_ANS_OPCODE, recv_DBtime_ans, 0, false},
|
||||
{TIME_SYNC_OPCODE, force_DBtime_sync, 0, false}
|
||||
#endif
|
||||
};
|
||||
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_lorasf, 1, true}, {0x06, set_lorapower, 1, true},
|
||||
{0x07, set_loraadr, 1, true}, {0x08, set_screensaver, 1, true},
|
||||
{0x09, set_reset, 1, true}, {0x0a, set_sendcycle, 1, true},
|
||||
{0x0b, set_wifichancycle, 1, true}, {0x0c, set_blescantime, 1, true},
|
||||
{0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true},
|
||||
{0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true},
|
||||
{0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false},
|
||||
{0x13, set_sensor, 2, true}, {0x80, get_config, 0, false},
|
||||
{0x81, get_status, 0, false}, {0x84, get_gps, 0, false},
|
||||
{0x85, get_bme, 0, false}, {0x86, get_time, 0, false},
|
||||
{0x87, set_time, 0, false}, {0x99, set_flush, 0, false}};
|
||||
|
||||
const uint8_t cmdtablesize =
|
||||
sizeof(table) / sizeof(table[0]); // number of commands in command table
|
||||
|
@ -160,12 +160,8 @@ void spi_enqueuedata(MessageBuffer_t *message, sendprio_t prio) {
|
||||
ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0);
|
||||
break;
|
||||
}
|
||||
if (ret == pdTRUE) {
|
||||
ESP_LOGI(TAG, "%d byte(s) enqueued for SPI interface",
|
||||
message->MessageSize);
|
||||
} else {
|
||||
if (ret != pdTRUE)
|
||||
ESP_LOGW(TAG, "SPI sendqueue is full");
|
||||
}
|
||||
}
|
||||
|
||||
void spi_queuereset(void) { xQueueReset(SPISendQueue); }
|
||||
|
@ -1,5 +1,13 @@
|
||||
#include "timekeeper.h"
|
||||
|
||||
#ifndef HAS_LORA
|
||||
#if (TIME_SYNC_TIMESERVER)
|
||||
#error TIME_SYNC_TIMESERVER defined, but device has no LORA configured
|
||||
#elif (TIME_SYNC_LORAWAN)
|
||||
#error TIME_SYNC_LORAWAN defined, but device has no LORA configured
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Local logging tag
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
@ -35,11 +43,11 @@ time_t timeProvider(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// kick off asychronous DB timesync if we have
|
||||
#if(DBTIMESYNC)
|
||||
send_DBtime_req();
|
||||
// kick off asychronous lora sync if we have
|
||||
#elif defined HAS_LORA && (TIME_SYNC_LORA)
|
||||
// kick off asychronous Lora timeserver timesync if we have
|
||||
#if (TIME_SYNC_TIMESERVER)
|
||||
send_timesync_req();
|
||||
// kick off asychronous lora network sync if we have
|
||||
#elif (TIME_SYNC_LORAWAN)
|
||||
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
|
||||
#endif
|
||||
|
||||
@ -108,7 +116,7 @@ void timepulse_start(void) {
|
||||
void IRAM_ATTR CLOCKIRQ(void) {
|
||||
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
SyncToPPS(); // calibrates UTC systime, see Time.h
|
||||
SyncToPPS(); // calibrates UTC systime, see microTime.h
|
||||
xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
if (ClockTask != NULL)
|
||||
@ -141,7 +149,7 @@ time_t compiledUTC(void) {
|
||||
time_t tmConvert(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh, uint8_t mm,
|
||||
uint8_t ss) {
|
||||
tmElements_t tm;
|
||||
tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in time.h
|
||||
tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in microTime.h
|
||||
tm.Month = MM;
|
||||
tm.Day = DD;
|
||||
tm.Hour = hh;
|
||||
|
181
src/timesync.cpp
Normal file
181
src/timesync.cpp
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
|
||||
///--> IMPORTANT LICENSE NOTE for 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.
|
||||
|
||||
*/
|
||||
|
||||
#ifdef TIME_SYNC_TIMESERVER
|
||||
|
||||
#include "timesync.h"
|
||||
|
||||
// Local logging tag
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
TaskHandle_t timeSyncReqTask;
|
||||
|
||||
static uint8_t time_sync_seqNo = 0;
|
||||
static bool lora_time_sync_pending = false;
|
||||
|
||||
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;
|
||||
|
||||
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||
|
||||
// send time request message
|
||||
void send_timesync_req() {
|
||||
|
||||
// if a timesync handshake is pending then exit
|
||||
if (lora_time_sync_pending) {
|
||||
ESP_LOGI(TAG, "Timeserver sync request already pending");
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Timeserver sync request started");
|
||||
|
||||
lora_time_sync_pending = true;
|
||||
|
||||
// clear timestamp array
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint(); // set to epoch
|
||||
}
|
||||
|
||||
// kick off temporary task for timeserver handshake processing
|
||||
if (!timeSyncReqTask)
|
||||
xTaskCreatePinnedToCore(process_timesync_req, // task function
|
||||
"timesync_req", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)1, // task parameter
|
||||
0, // priority of the task
|
||||
&timeSyncReqTask, // task handle
|
||||
1); // CPU core
|
||||
}
|
||||
}
|
||||
|
||||
// process timeserver timestamp answer, called from lorawan.cpp
|
||||
void recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
|
||||
|
||||
// if no timesync handshake is pending or spurious buffer then exit
|
||||
if ((!lora_time_sync_pending) || (buf_len != TIME_SYNC_FRAME_LENGTH))
|
||||
return;
|
||||
|
||||
uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES;
|
||||
uint16_t timestamp_msec = 4 * buf[5]; // convert 1/250th sec fractions to ms
|
||||
uint32_t timestamp_sec = 0, tmp_sec = 0;
|
||||
|
||||
for (uint8_t i = 1; i <= 4; i++) {
|
||||
timestamp_sec = (tmp_sec <<= 8) |= buf[i];
|
||||
}
|
||||
|
||||
time_sync_rx[k] += std::chrono::seconds(timestamp_sec) +
|
||||
std::chrono::milliseconds(timestamp_msec);
|
||||
|
||||
ESP_LOGD(TAG, "Timesync request #%d rcvd at %d", seq_no,
|
||||
myClock::to_time_t(time_sync_rx[k]));
|
||||
|
||||
// inform processing task
|
||||
if (timeSyncReqTask)
|
||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||
}
|
||||
|
||||
// task for sending time sync requests
|
||||
void process_timesync_req(void *taskparameter) {
|
||||
|
||||
time_t time_to_set = 0;
|
||||
uint8_t k = 0, i = 0;
|
||||
uint32_t seq_no = 0;
|
||||
auto time_offset = myClock_msecTick::zero();
|
||||
|
||||
// enqueue timestamp samples in lora sendqueue
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||
|
||||
// wrap around seqNo 0 .. 254
|
||||
time_sync_seqNo = (time_sync_seqNo >= 255) ? 0 : time_sync_seqNo + 1;
|
||||
|
||||
// send sync request to server
|
||||
payload.reset();
|
||||
payload.addByte(time_sync_seqNo);
|
||||
SendPayload(TIMEPORT, prio_high);
|
||||
|
||||
// process answer
|
||||
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
||||
(seq_no != time_sync_seqNo)) {
|
||||
|
||||
ESP_LOGW(TAG, "Timeserver handshake failed");
|
||||
goto finish;
|
||||
} // no valid sequence received before timeout
|
||||
|
||||
else { // calculate time diff from collected timestamps
|
||||
k = seq_no % TIME_SYNC_SAMPLES;
|
||||
|
||||
auto t_tx = std::chrono::time_point_cast<std::chrono::milliseconds>(
|
||||
time_sync_tx[k]); // timepoint when node TX_completed
|
||||
auto t_rx = std::chrono::time_point_cast<std::chrono::milliseconds>(
|
||||
time_sync_rx[k]); // timepoint when message was seen on gateway
|
||||
|
||||
time_offset += t_rx - t_tx; // cumulate timepoint diffs
|
||||
|
||||
if (i < TIME_SYNC_SAMPLES - 1) // wait until next cycle
|
||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||
}
|
||||
} // for
|
||||
|
||||
// calculate time offset from collected diffs and set time if necessary
|
||||
time_offset /= TIME_SYNC_SAMPLES;
|
||||
ESP_LOGD(TAG, "Avg time diff: %lldms", time_offset.count());
|
||||
|
||||
if (abs(time_offset.count()) >= TIME_SYNC_TRIGGER) {
|
||||
|
||||
/*
|
||||
// wait until top of second
|
||||
if (time_offset_ms > 0) // clock is fast
|
||||
vTaskDelay(pdMS_TO_TICKS(time_diff_ms));
|
||||
else if (time_offset_ms < 0) // clock is slow
|
||||
vTaskDelay(pdMS_TO_TICKS(1000 + time_offset_ms));
|
||||
|
||||
time_to_set = t - time_t(time_offset_sec + 1);
|
||||
*/
|
||||
|
||||
time_t time_to_set = myClock::to_time_t(myClock::now() + time_offset);
|
||||
ESP_LOGD(TAG, "New UTC epoch time: %d", time_to_set);
|
||||
|
||||
// adjust system time
|
||||
if (timeIsValid(time_to_set)) {
|
||||
setTime(time_to_set);
|
||||
SyncToPPS();
|
||||
timeSource = _lora;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60,
|
||||
timeSync); // set to regular repeat
|
||||
ESP_LOGI(TAG, "Timesync finished, time was adjusted");
|
||||
} else
|
||||
ESP_LOGW(TAG, "Invalid time received from timeserver");
|
||||
} else
|
||||
ESP_LOGI(TAG, "Timesync finished, time is up to date");
|
||||
|
||||
finish:
|
||||
|
||||
lora_time_sync_pending = false;
|
||||
timeSyncReqTask = NULL;
|
||||
vTaskDelete(NULL); // end task
|
||||
}
|
||||
|
||||
// called from lorawan.cpp after time_sync_req was sent
|
||||
void store_time_sync_req(time_t t_sec, uint32_t t_microsec) {
|
||||
|
||||
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
||||
|
||||
time_sync_tx[k] +=
|
||||
std::chrono::seconds(t_sec) + std::chrono::microseconds(t_microsec);
|
||||
|
||||
ESP_LOGD(TAG, "Timesync request #%d sent at %d", time_sync_seqNo,
|
||||
myClock::to_time_t(time_sync_tx[k]));
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user