From c64f087faaeedb2cf31990144505e11d638bcead Mon Sep 17 00:00:00 2001 From: Verkehrsrot Date: Sat, 9 Mar 2019 15:25:44 +0100 Subject: [PATCH] timesync (experimental) --- include/payload.h | 11 ++-- include/timesync.h | 22 +++++--- platformio.ini | 8 +-- src/irqhandler.cpp | 7 ++- src/lorawan.cpp | 15 +++--- src/macsniff.cpp | 2 +- src/paxcounter.conf | 2 +- src/payload.cpp | 27 ++++++++-- src/rcommand.cpp | 2 +- src/timesync.cpp | 126 +++++++++++++++++++++++++++++--------------- 10 files changed, 149 insertions(+), 73 deletions(-) diff --git a/include/payload.h b/include/payload.h index b93347d9..fa7d05d8 100644 --- a/include/payload.h +++ b/include/payload.h @@ -16,11 +16,12 @@ #define LPP_MSG_CHANNEL 28 #define LPP_HUMIDITY_CHANNEL 29 #define LPP_BAROMETER_CHANNEL 30 -#define LPP_AIR_CHANNEL 31 +#define LPP_AIR_CHANNEL 31 #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,11 +41,13 @@ public: void reset(void); uint8_t getSize(void); uint8_t *getBuffer(void); + void addByte(uint8_t value); + void addWord(uint16_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, uint8_t reset1, uint8_t reset2); - void add2Bytes(int8_t rssi, uint8_t message); + void addAlarm(int8_t rssi, uint8_t message); void addVoltage(uint16_t value); void addGPS(gpsStatus_t value); void addBME(bmeStatus_t value); @@ -72,7 +75,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); diff --git a/include/timesync.h b/include/timesync.h index 60d005f1..ad0974b8 100644 --- a/include/timesync.h +++ b/include/timesync.h @@ -7,19 +7,27 @@ #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 30 // timeout seconds waiting for timeserver answer -#define TIME_SYNC_THRESHOLD 1 // time deviation threshold triggering time sync -#define TIME_SYNC_START_OPCODE 0x90 // force time sync on node -#define TIME_SYNC_REQ_OPCODE 0x92 // node requests time at server -#define TIME_SYNC_ANS_OPCODE 0x93 // server answers time to node +#define TIME_SYNC_TIMEOUT 180 // timeout seconds waiting for timeserver answer +#define TIME_SYNC_THRESHOLD \ + 1.0f // time deviation threshold triggering time sync +#define TIME_SYNC_START_OPCODE 0x90 // start time sync on node +#define TIME_SYNC_STOP_OPCODE 0x91 // stop time sync on node +#define TIME_SYNC_REQ_OPCODE 0x92 // node request at timeserver +#define TIME_SYNC_ANS_OPCODE 0x93 // timeserver answer to node -extern uint32_t time_sync_messages[], time_sync_answers[]; -extern uint8_t volatile time_sync_seqNo; +typedef struct { + uint32_t seconds; + uint8_t fractions; // 1/250ths second = 4 milliseconds resolution +} time_sync_message_t; + +extern time_sync_message_t time_sync_messages[], time_sync_answers[]; +extern uint8_t time_sync_seqNo; void send_Servertime_req(void); void recv_Servertime_ans(uint8_t val[]); void process_Servertime_sync_req(void *taskparameter); void process_Servertime_sync_ans(void *taskparameter); void force_Servertime_sync(uint8_t val[]); +void store_time_sync_req(time_t secs, uint32_t micros); #endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 6c6fb377..32d4b9d7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -6,7 +6,7 @@ ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] -env_default = generic +;env_default = generic ;env_default = ebox ;env_default = eboxtube ;env_default = heltec @@ -15,7 +15,7 @@ env_default = generic ;env_default = ttgov2 ;env_default = ttgov21old ;env_default = ttgov21new -;env_default = ttgobeam +env_default = ttgobeam ;env_default = ttgofox ;env_default = lopy ;env_default = lopy4 @@ -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.361 +release_version = 1.7.37 ; 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 diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp index aba502ae..bbfdafca 100644 --- a/src/irqhandler.cpp +++ b/src/irqhandler.cpp @@ -35,8 +35,11 @@ void irqHandler(void *pvParameters) { #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? diff --git a/src/lorawan.cpp b/src/lorawan.cpp index 50cb68b2..f0c653ab 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -175,6 +175,8 @@ void showLoraKeys(void) { void onEvent(ev_t ev) { char buff[24] = ""; + uint32_t now_micros; + switch (ev) { case EV_SCAN_TIMEOUT: @@ -227,11 +229,10 @@ void onEvent(ev_t ev) { #if (TIME_SYNC_TIMESERVER) // if last packet sent was a timesync request was sent, store TX timestamp - if (LMIC.pendTxPort == TIMEPORT) { - time_sync_messages[time_sync_seqNo] = osticks2ms(LMIC.txend); - ESP_LOGD(TAG, "Timeserver request #%d was sent at %d", - time_sync_seqNo, time_sync_messages[time_sync_seqNo]); - } + if ((LMIC.pendTxPort == TIMEPORT) && + (LMIC.pendTxData[0] == TIME_SYNC_REQ_OPCODE)) + store_time_sync_req(now(now_micros), now_micros); + // maybe using more precise osticks2ms(LMIC.txend) here? #endif strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED_ACK") @@ -247,7 +248,6 @@ void onEvent(ev_t ev) { if ((LMIC.txrxFlags & TXRX_PORT) && (LMIC.frame[LMIC.dataBeg - 1] == RCMDPORT)) rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen); - } break; @@ -462,8 +462,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(); diff --git a/src/macsniff.cpp b/src/macsniff.cpp index fc5fb910..cd20e39a 100644 --- a/src/macsniff.cpp +++ b/src/macsniff.cpp @@ -106,7 +106,7 @@ bool mac_add(uint8_t *paddr, int8_t rssi, bool sniff_type) { blink_LED(COLOR_WHITE, 2000); #endif payload.reset(); - payload.add2Bytes(rssi, beaconID); + payload.addAlarm(rssi, beaconID); SendPayload(BEACONPORT, prio_high); } }; diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 0e5d3fee..46d392ae 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -68,7 +68,7 @@ // 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_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 0] -#define TIME_SYNC_TIMESERVER 0 // set to 1 to use LORA timeserver 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 diff --git a/src/payload.cpp b/src/payload.cpp index 06ced707..b5f66378 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -18,12 +18,19 @@ uint8_t *PayloadConvert::getBuffer(void) { return buffer; } #if PAYLOAD_ENCODER == 1 +void PayloadConvert::addByte(uint8_t value) { buffer[cursor++] = (value); } + +void PayloadConvert::addWord(uint16_t value) { + buffer[cursor++] = lowByte(value); + buffer[cursor++] = highByte(value); +} + void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { buffer[cursor++] = highByte(value); buffer[cursor++] = lowByte(value); } -void PayloadConvert::add2Bytes(int8_t rssi, uint8_t msg) { +void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) { buffer[cursor++] = rssi; buffer[cursor++] = msg; } @@ -141,11 +148,15 @@ void PayloadConvert::addTime(time_t value) { #elif PAYLOAD_ENCODER == 2 +void PayloadConvert::addByte(uint8_t value) { writeUint8(value); } + +void PayloadConvert::addWord(uint16_t value) { writeUint16(value); } + void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { writeUint16(value); } -void PayloadConvert::add2Bytes(int8_t rssi, uint8_t msg) { +void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) { writeUint8(rssi); writeUint8(msg); } @@ -299,6 +310,16 @@ 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::addWord(uint16_t value) { + /* + not implemented + */ } + void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { switch (snifftype) { case MAC_SNIFF_WIFI: @@ -322,7 +343,7 @@ void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) { } } -void PayloadConvert::add2Bytes(int8_t rssi, uint8_t msg) { +void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) { #if (PAYLOAD_ENCODER == 3) buffer[cursor++] = LPP_ALARM_CHANNEL; #endif diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 1b04a2ea..f613e0b3 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -308,7 +308,7 @@ cmd_t table[] = {{0x01, set_rssi, 1, true}, {0x86, get_time, 0, false} #if(TIME_SYNC_TIMESERVER) , - {TIME_SYNC_ANS_OPCODE, recv_Servertime_ans, 5, false}, + {TIME_SYNC_ANS_OPCODE, recv_Servertime_ans, 6, false}, {TIME_SYNC_START_OPCODE, force_Servertime_sync, 0, false} #endif }; diff --git a/src/timesync.cpp b/src/timesync.cpp index 3f3200ab..0ff75253 100644 --- a/src/timesync.cpp +++ b/src/timesync.cpp @@ -18,10 +18,11 @@ patent holder. static const char TAG[] = __FILE__; TaskHandle_t timeSyncReqTask, timeSyncAnsTask; -uint32_t time_sync_messages[TIME_SYNC_SAMPLES + - 1] = {0}, - time_sync_answers[TIME_SYNC_SAMPLES + 1] = {0}; -uint8_t volatile time_sync_seqNo = 0; // used in lorawan.cpp to store timestamp +time_sync_message_t + time_sync_messages[TIME_SYNC_SAMPLES + 1] = {0}, + time_sync_answers[TIME_SYNC_SAMPLES + + 1] = {0}; +uint8_t time_sync_seqNo = 0; // used in lorawan.cpp to store timestamp // send time request message void send_Servertime_req() { @@ -35,9 +36,12 @@ void send_Servertime_req() { // clear timestamp array for (uint8_t i = 0; i <= TIME_SYNC_SAMPLES + 1; i++) { - time_sync_messages[i] = time_sync_answers[i] = 0; + time_sync_messages[i].seconds = time_sync_answers[i].seconds = 0; + time_sync_messages[i].fractions = time_sync_answers[i].fractions = 0; } + time_sync_seqNo = 0; + // create temporary task for processing sync answers if not already active if (!timeSyncAnsTask) xTaskCreatePinnedToCore(process_Servertime_sync_ans, // task function @@ -68,14 +72,16 @@ void recv_Servertime_ans(uint8_t val[]) { return; uint8_t seq_no = val[0]; - uint32_t timestamp = 0; + uint32_t timestamp_sec = 0, timestamp_ms = 0; - for (int i = 1; i <= 4; i++) - timestamp = (timestamp << 8) | val[i]; - time_sync_answers[seq_no] = timestamp; + for (int i = 1; i <= 4; i++) { + timestamp_sec = (timestamp_sec << 8) | val[i]; + time_sync_answers[seq_no].seconds = timestamp_sec; + } + time_sync_answers[seq_no].fractions = val[5]; - ESP_LOGD(TAG, "Timeserver timestamp #%d received: time=%d", seq_no, - timestamp); + ESP_LOGD(TAG, "Timeserver timestamp #%d received: time=%d.%d", seq_no, + timestamp_sec, timestamp_ms); // inform processing task if (timeSyncAnsTask) @@ -88,59 +94,95 @@ void process_Servertime_sync_req(void *taskparameter) { for (uint8_t i = 1; i <= TIME_SYNC_SAMPLES; i++) { time_sync_seqNo++; payload.reset(); - payload.add2Bytes(TIME_SYNC_REQ_OPCODE, i); + payload.addWord(TIME_SYNC_REQ_OPCODE | time_sync_seqNo << 8); SendPayload(TIMEPORT, prio_high); - ESP_LOGD(TAG, "Timeserver request #%d sent", i); + ESP_LOGD(TAG, "Timeserver request #%d sent", time_sync_seqNo); // Wait for the next cycle vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000)); } + payload.reset(); + payload.addByte(TIME_SYNC_STOP_OPCODE); + SendPayload(TIMEPORT, prio_high); // necessary to receive last timestamp timeSyncReqTask = NULL; vTaskDelete(NULL); // end task } // task for processing a timesync handshake void process_Servertime_sync_ans(void *taskparameter) { - uint32_t seq_no = 0; - uint32_t NetworkTime = 0; - int32_t time_diff = 0; - uint8_t ans_counter = TIME_SYNC_SAMPLES; + + uint32_t seq_no = 0, time_diff_sec = 0, time_diff_ms = 0; + time_t time_to_set = 0; + float time_offset = 0.0f; // collect incoming timestamp samples notified by rcommand for (uint8_t i = 1; i <= TIME_SYNC_SAMPLES; i++) { - if (xTaskNotifyWait(0x00, ULONG_MAX, &seq_no, - (TIME_SYNC_CYCLE + TIME_SYNC_TIMEOUT) * 1000 / - portTICK_PERIOD_MS) == pdFALSE) - continue; // no answer received before timeout + if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no, + (TIME_SYNC_CYCLE + TIME_SYNC_TIMEOUT) * 1000 / + portTICK_PERIOD_MS) == pdFALSE) || + (seq_no != i)) { + ESP_LOGW(TAG, "Timesync handshake timeout"); + goto finish; // no valid sequence received before timeout + } else // calculate time diff from set of collected timestamps + { + time_diff_sec += time_sync_messages[seq_no].seconds - + time_sync_answers[seq_no].seconds; + time_diff_ms += 4 * (time_sync_messages[seq_no].fractions - + time_sync_answers[seq_no].fractions); + } + } // for - time_diff += time_sync_messages[seq_no] - time_sync_answers[seq_no]; - ans_counter--; - } + time_offset = (time_diff_sec + time_diff_ms / 1000.0) / TIME_SYNC_SAMPLES; - if (ans_counter) { - ESP_LOGW(TAG, "Timesync handshake timeout"); - } else { - // calculate time diff from set of collected timestamps - if (time_diff / TIME_SYNC_SAMPLES) { - NetworkTime = now() + time_diff; - ESP_LOGI(TAG, "Timesync finished, time offset=%d seconds", time_diff); - // Update system time with time read from the network - if (timeIsValid(NetworkTime)) { - setTime(NetworkTime); - timeSource = _lora; - timesyncer.attach(TIME_SYNC_INTERVAL * 60, - timeSync); // set to regular repeat - ESP_LOGI(TAG, "Recent time received from timeserver"); - } else - ESP_LOGW(TAG, "Invalid time received from timeserver"); + ESP_LOGD(TAG, "Timesync finished, time offset=%.4f seconds", time_offset); + + // check time diff and if necessary set time + if (time_offset >= TIME_SYNC_THRESHOLD) { + + // wait until top of second + if (time_diff_ms > 0) { + vTaskDelay(1000 - time_diff_ms); // clock is fast + time_diff_sec--; + } else if (time_diff_ms < 0) { // clock is stale + vTaskDelay(1000 + time_diff_ms); + time_diff_sec++; + } + + time_to_set = time_t(now() - time_diff_sec); + + ESP_LOGD(TAG, "Time to set = %d", time_to_set); + + // Update system time with time read from the network + 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_LOGI(TAG, "Timesync finished, time is up to date"); - } // if (ans_counter) + ESP_LOGW(TAG, "Invalid time received from timeserver"); + } else + ESP_LOGI(TAG, "Timesync finished, time is up to date"); + +finish: time_sync_seqNo = 0; timeSyncAnsTask = NULL; vTaskDelete(NULL); // end task } +// called from lorawan.cpp when tine_sync_req was sent +void store_time_sync_req(time_t secs, uint32_t micros) { + + time_sync_messages[time_sync_seqNo].seconds = secs; + time_sync_messages[time_sync_seqNo].fractions = + micros / 250; // 4ms resolution + + ESP_LOGD(TAG, "Timeserver request #%d was sent at %d.%d", time_sync_seqNo, + time_sync_messages[time_sync_seqNo].seconds, + time_sync_messages[time_sync_seqNo].fractions); +} + void force_Servertime_sync(uint8_t val[]) { ESP_LOGI(TAG, "Timesync requested by timeserver"); timeSync();