Merge pull request #330 from cyberman54/development
Several small fixes from development
This commit is contained in:
commit
2c1205d61a
@ -162,7 +162,7 @@ Output of sensor and peripheral data is internally switched by a bitmask registe
|
||||
|
||||
# Time sync
|
||||
|
||||
Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the [**Timeserver code**](/src/Timeserver/Nodered-Timeserver.json) in TTN subdirectory. Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using.
|
||||
Paxcounter can keep it's time-of-day synced with an external time source. Set *#define TIME_SYNC_INTERVAL* in paxcounter.conf to enable time sync. Supported external time sources are GPS, LORAWAN network time and LORAWAN application timeserver time. An on board DS3231 RTC is kept sycned as fallback time source. Time accuracy depends on board's time base which generates the pulse per second. Supported are GPS PPS, SQW output of RTC, and internal ESP32 hardware timer. Time base is selected by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h). Bonus: If your LORAWAN network does not support network time, you can run a Node-Red timeserver application using the enclosed [**Timeserver code**](/src/Timeserver/Nodered-Timeserver.json). Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using.
|
||||
|
||||
# Wall clock controller
|
||||
|
||||
@ -389,7 +389,7 @@ Note: all settings are stored in NVRAM and will be reloaded when device starts.
|
||||
|
||||
0x86 get time/date
|
||||
|
||||
Device answers with it's local time/date (UTC Unix epoch) on Port 9.
|
||||
Device answers with it's local time/date (UTC Unix epoch) on Port 2.
|
||||
|
||||
0x87 set time/date
|
||||
|
||||
|
16
include/mobaserial.h
Normal file
16
include/mobaserial.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef _MOBALINE_H
|
||||
#define _MOBALINE_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "dcf77.h"
|
||||
|
||||
#define MOBALINE_FRAME_SIZE (33)
|
||||
#define MOBALINE_PULSE_LENGTH (100)
|
||||
#define MOBALINE_HEAD_PULSE_LENGTH (1500)
|
||||
|
||||
void MOBALINE_Pulse(time_t t, uint8_t const *DCFpulse);
|
||||
uint8_t *IRAM_ATTR MOBALINE_Frame(time_t const t);
|
||||
void IRAM_ATTR dec2bcd(uint8_t const dec, uint8_t const startpos,
|
||||
uint8_t const endpos, uint8_t *DCFpulse);
|
||||
|
||||
#endif
|
@ -14,5 +14,6 @@ void send_timesync_req(void);
|
||||
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len);
|
||||
void process_timesync_req(void *taskparameter);
|
||||
void store_time_sync_req(uint32_t t_millisec);
|
||||
int adjustTime(uint32_t t_sec, uint16_t t_msec);
|
||||
|
||||
#endif
|
@ -18,6 +18,7 @@ function Converter(decoded, port) {
|
||||
}
|
||||
|
||||
if (port === 2) {
|
||||
if('voltage' in converted)
|
||||
converted.voltage /= 1000;
|
||||
}
|
||||
|
||||
|
@ -30,8 +30,14 @@ function Decoder(bytes, port) {
|
||||
|
||||
if (port === 2) {
|
||||
// device status data
|
||||
if (bytes.length === 17) {
|
||||
return decode(bytes, [uint16, uptime, uint8, uint32, uint8, uint8], ['voltage', 'uptime', 'cputemp', 'memory', 'reset0', 'reset1']);
|
||||
}
|
||||
// epoch time answer
|
||||
if (bytes.length === 4) {
|
||||
return decode(bytes, [uint32], ['time']);
|
||||
}
|
||||
}
|
||||
|
||||
if (port === 3) {
|
||||
// device config data
|
||||
|
@ -462,7 +462,7 @@ void lora_housekeeping(void) {
|
||||
// uxTaskGetStackHighWaterMark(LoraTask));
|
||||
}
|
||||
|
||||
void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
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;
|
||||
@ -487,6 +487,11 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
return;
|
||||
}
|
||||
|
||||
// begin of time critical section: lock I2C bus to ensure accurate timing
|
||||
// don't move the mutex, will impact accuracy of time up to 1 sec!
|
||||
if (!I2C_MUTEX_LOCK())
|
||||
return; // failure
|
||||
|
||||
// Update userUTCTime, considering the difference between the GPS and UTC
|
||||
// time, and the leap seconds until year 2019
|
||||
*pUserUTCTime = lmicTimeReference.tNetwork + 315964800;
|
||||
@ -497,19 +502,13 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
// Add the delay between the instant the time was transmitted and
|
||||
// the current time
|
||||
time_t requestDelaySec = osticks2ms(ticksNow - ticksRequestSent) / 1000;
|
||||
*pUserUTCTime += requestDelaySec;
|
||||
|
||||
// Update system time with time read from the network
|
||||
if (timeIsValid(*pUserUTCTime)) {
|
||||
setTime(*pUserUTCTime);
|
||||
#ifdef HAS_RTC
|
||||
set_rtctime(*pUserUTCTime, do_mutex); // calibrate RTC if we have one
|
||||
#endif
|
||||
timeSource = _lora;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat
|
||||
ESP_LOGI(TAG, "Received recent time from LoRa");
|
||||
} else
|
||||
ESP_LOGI(TAG, "Invalid time received from LoRa");
|
||||
adjustTime(*pUserUTCTime + requestDelaySec, 0);
|
||||
|
||||
// end of time critical section: release I2C bus
|
||||
I2C_MUTEX_UNLOCK();
|
||||
|
||||
} // user_request_network_time_callback
|
||||
|
||||
#endif // HAS_LORA
|
||||
|
@ -31,7 +31,7 @@ ledloop 0 3 blinks LEDs
|
||||
spiloop 0 2 reads/writes data on spi interface
|
||||
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
|
||||
|
||||
clockloop 1 4 generates realtime telegrams for external clock
|
||||
clockloop 1 3 generates realtime telegrams for external clock
|
||||
looptask 1 1 arduino core -> runs the LMIC LoRa stack
|
||||
irqhandler 1 1 executes tasks triggered by timer irq
|
||||
gpsloop 1 2 reads data from GPS via serial or i2c
|
||||
|
109
src/mobaserial.cpp
Normal file
109
src/mobaserial.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
// Emulate a MOBATIME serial clock controller
|
||||
//
|
||||
// Protocol published and described here:
|
||||
//
|
||||
//
|
||||
http://www.elektrorevue.cz/cz/download/time-distribution-within-industry-4-0-platform--controlling-slave-clocks-via-master-clock-hn50/
|
||||
*/
|
||||
|
||||
#ifdef HAS_MOBALINE
|
||||
|
||||
#include "mobaline.h"
|
||||
|
||||
// Local logging tag
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
// triggered by pulse per second to ticker out mobaline frame
|
||||
void MOBALINE_Pulse(time_t t, uint8_t const *DCFpulse) {
|
||||
|
||||
TickType_t startTime = xTaskGetTickCount();
|
||||
uint8_t sec = second(t);
|
||||
|
||||
t = myTZ.toLocal(now());
|
||||
ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] MOBALINE bit %d", hour(t), minute(t),
|
||||
second(t), millisecond(), sec);
|
||||
|
||||
// induce 3 pulses
|
||||
for (uint8_t pulse = 0; pulse <= 3; pulse++) {
|
||||
|
||||
switch (pulse) {
|
||||
|
||||
case 0: // start of bit -> start of timeframe for logic signal
|
||||
if (DCFpulse[sec] != dcf_Z) {
|
||||
digitalWrite(HAS_DCF77, dcf_high);
|
||||
vTaskDelay(pdMS_TO_TICKS(MOBALINE_HEAD_PULSE_LENGTH));
|
||||
digitalWrite(HAS_DCF77, dcf_high);
|
||||
vTaskDelay(pdMS_TO_TICKS(MOBALINE_HEAD_PULSE_LENGTH));
|
||||
return; // next bit
|
||||
} else // start the signalling for the next bit
|
||||
digitalWrite(HAS_DCF77, dcf_high);
|
||||
break;
|
||||
|
||||
case 1: // 100ms after start of bit -> end of timeframe for logic 0
|
||||
if (DCFpulse[sec] == dcf_1)
|
||||
digitalWrite(HAS_DCF77, dcf_low);
|
||||
break;
|
||||
|
||||
case 2: // 200ms after start of bit -> end of timeframe for logic 1
|
||||
if (DCFpulse[sec] == dcf_0)
|
||||
digitalWrite(HAS_DCF77, dcf_low);
|
||||
break;
|
||||
|
||||
case 3: // 300ms after start -> last pulse
|
||||
break;
|
||||
|
||||
} // switch
|
||||
|
||||
// pulse pause
|
||||
vTaskDelayUntil(&startTime, pdMS_TO_TICKS(MOBALINE_PULSE_LENGTH));
|
||||
|
||||
} // for
|
||||
} // DCF77_Pulse()
|
||||
|
||||
uint8_t *IRAM_ATTR MOBALINE_Frame(time_t const tt) {
|
||||
|
||||
// array of dcf pulses for one minute, secs 0..16 and 20 are never touched, so
|
||||
// we keep them statically to avoid same recalculation every minute
|
||||
|
||||
static uint8_t DCFpulse[DCF77_FRAME_SIZE + 1];
|
||||
|
||||
time_t t = myTZ.toLocal(tt); // convert to local time
|
||||
|
||||
// ENCODE HEAD (bit 0))
|
||||
DCFpulse[0] = dcf_Z; // not yet implemented
|
||||
|
||||
// ENCODE DAYLIGHTSAVING (bit 1)
|
||||
DCFpulse[1] = myTZ.locIsDST(t) ? dcf_1 : dcf_0;
|
||||
|
||||
// ENCODE DATE (bits 2..20)
|
||||
dec2bcd(false, year(t) - 2000, 2, 9, DCFpulse);
|
||||
dec2bcd(false, month(t), 10, 14, DCFpulse);
|
||||
dec2bcd(false, day(t), 15, 20, DCFpulse);
|
||||
|
||||
// ENCODE HOUR (bits 21..26)
|
||||
dec2bcd2(false, hour(t), 21, 26, DCFpulse);
|
||||
|
||||
// ENCODE MINUTE (bits 27..33)
|
||||
dec2bcd2(false, minute(t), 27, 33, DCFpulse);
|
||||
|
||||
// timestamp this frame with it's minute
|
||||
DCFpulse[34] = minute(t);
|
||||
|
||||
return DCFpulse;
|
||||
|
||||
} // MOBALINE_Frame()
|
||||
|
||||
// helper function to convert decimal to bcd digit msb
|
||||
void IRAM_ATTR dec2bcd(uint8_t const dec, uint8_t const startpos,
|
||||
uint8_t const endpos, uint8_t *DCFpulse) {
|
||||
|
||||
uint8_t data = (dec < 10) ? dec : ((dec / 10) << 4) + (dec % 10);
|
||||
|
||||
for (uint8_t i = endpos; i >= startpos; i--) {
|
||||
DCFpulse[i] = (data & 1) ? dcf_1 : dcf_0;
|
||||
data >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HAS_MOBALINE
|
@ -277,7 +277,7 @@ void get_time(uint8_t val[]) {
|
||||
ESP_LOGI(TAG, "Remote command: get time");
|
||||
payload.reset();
|
||||
payload.addTime(now());
|
||||
SendPayload(TIMEPORT, prio_high);
|
||||
SendPayload(STATUSPORT, prio_high);
|
||||
};
|
||||
|
||||
void set_time(uint8_t val[]) {
|
||||
|
@ -196,7 +196,7 @@ void clock_init(void) {
|
||||
"clockloop", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)&userUTCTime, // start time as task parameter
|
||||
4, // priority of the task
|
||||
3, // priority of the task
|
||||
&ClockTask, // task handle
|
||||
1); // CPU core
|
||||
|
||||
|
106
src/timesync.cpp
106
src/timesync.cpp
@ -53,7 +53,7 @@ void send_timesync_req() {
|
||||
"timesync_req", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)1, // task parameter
|
||||
4, // priority of the task
|
||||
2, // priority of the task
|
||||
&timeSyncReqTask, // task handle
|
||||
1); // CPU core
|
||||
}
|
||||
@ -62,10 +62,9 @@ void send_timesync_req() {
|
||||
// task for sending time sync requests
|
||||
void process_timesync_req(void *taskparameter) {
|
||||
|
||||
uint8_t k = 0, i = 0;
|
||||
uint8_t k = 0;
|
||||
uint16_t time_to_set_fraction_msec;
|
||||
uint32_t seq_no = 0;
|
||||
time_t time_to_set;
|
||||
uint32_t seq_no = 0, time_to_set;
|
||||
auto time_offset_ms = myClock_msecTick::zero();
|
||||
|
||||
// wait until we are joined
|
||||
@ -87,12 +86,8 @@ void process_timesync_req(void *taskparameter) {
|
||||
// process answer, wait for notification from recv_timesync_ans()
|
||||
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
||||
(seq_no != time_sync_seqNo)) {
|
||||
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
||||
millis() / 1000.0);
|
||||
goto finish;
|
||||
} // no valid sequence received before timeout
|
||||
(seq_no != time_sync_seqNo))
|
||||
goto error; // no valid sequence received before timeout
|
||||
|
||||
else { // calculate time diff from collected timestamps
|
||||
k = seq_no % TIME_SYNC_SAMPLES;
|
||||
@ -116,7 +111,9 @@ void process_timesync_req(void *taskparameter) {
|
||||
} // for
|
||||
|
||||
// begin of time critical section: lock I2C bus to ensure accurate timing
|
||||
I2C_MUTEX_LOCK();
|
||||
// don't move the mutex, will impact accuracy of time up to 1 sec!
|
||||
if (!I2C_MUTEX_LOCK())
|
||||
goto error; // failure
|
||||
|
||||
// average time offset from collected diffs
|
||||
time_offset_ms /= TIME_SYNC_SAMPLES;
|
||||
@ -127,63 +124,38 @@ void process_timesync_req(void *taskparameter) {
|
||||
time_offset_ms +=
|
||||
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
|
||||
|
||||
// calculate absolute time in UTC epoch
|
||||
// convert to whole seconds, floor
|
||||
time_to_set = (time_t)(time_offset_ms.count() / 1000) + 1;
|
||||
// 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);
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] Calculated UTC epoch time: %d.%03d sec",
|
||||
millis() / 1000.0, time_to_set, time_to_set_fraction_msec);
|
||||
|
||||
// adjust system time
|
||||
if (timeIsValid(time_to_set)) {
|
||||
|
||||
// wait until top of second with 4ms precision
|
||||
vTaskDelay(pdMS_TO_TICKS(1000 - time_to_set_fraction_msec));
|
||||
|
||||
#ifdef HAS_RTC
|
||||
time_to_set++; // advance time 1 sec wait time
|
||||
// set RTC time and calibrate RTC_INT pulse on top of second
|
||||
set_rtctime(time_to_set, no_mutex);
|
||||
#endif
|
||||
|
||||
#if (!defined GPS_INT && !defined RTC_INT)
|
||||
// sync pps timer to top of second
|
||||
timerRestart(ppsIRQ); // reset pps timer
|
||||
CLOCKIRQ(); // fire clock pps, advances time 1 sec
|
||||
#endif
|
||||
|
||||
setTime(time_to_set); // set the time on top of second
|
||||
adjustTime(time_to_set, time_to_set_fraction_msec);
|
||||
|
||||
// end of time critical section: release I2C bus
|
||||
I2C_MUTEX_UNLOCK();
|
||||
|
||||
timeSource = _lora;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was adjusted",
|
||||
millis() / 1000.0);
|
||||
} else
|
||||
ESP_LOGW(TAG, "[%0.3f] Timesync failed, outdated time calculated",
|
||||
millis() / 1000.0);
|
||||
|
||||
finish:
|
||||
|
||||
lora_time_sync_pending = false;
|
||||
timeSyncReqTask = NULL;
|
||||
vTaskDelete(NULL); // end task
|
||||
|
||||
error:
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
||||
millis() / 1000.0);
|
||||
goto finish; // end task
|
||||
}
|
||||
|
||||
// called from lorawan.cpp after time_sync_req was sent
|
||||
void store_time_sync_req(uint32_t t_txEnd_ms) {
|
||||
void store_time_sync_req(uint32_t timestamp) {
|
||||
|
||||
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
||||
|
||||
time_sync_tx[k] += milliseconds(t_txEnd_ms);
|
||||
time_sync_tx[k] += milliseconds(timestamp);
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
|
||||
millis() / 1000.0, time_sync_seqNo, t_txEnd_ms / 1000,
|
||||
t_txEnd_ms % 1000);
|
||||
millis() / 1000.0, time_sync_seqNo, timestamp / 1000,
|
||||
timestamp % 1000);
|
||||
}
|
||||
|
||||
// process timeserver timestamp answer, called from lorawan.cpp
|
||||
@ -240,4 +212,40 @@ int recv_timesync_ans(uint8_t buf[], uint8_t buf_len) {
|
||||
}
|
||||
}
|
||||
|
||||
// adjust system time, calibrate RTC and RTC_INT pps
|
||||
int IRAM_ATTR adjustTime(uint32_t t_sec, uint16_t t_msec) {
|
||||
|
||||
time_t time_to_set = (time_t)t_sec;
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] Calculated UTC epoch time: %d.%03d sec",
|
||||
millis() / 1000.0, time_to_set, t_msec);
|
||||
|
||||
if (timeIsValid(time_to_set)) {
|
||||
|
||||
// wait until top of second with millisecond precision
|
||||
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec));
|
||||
|
||||
#ifdef HAS_RTC
|
||||
time_to_set++; // advance time 1 sec wait time
|
||||
// set RTC time and calibrate RTC_INT pulse on top of second
|
||||
set_rtctime(time_to_set, no_mutex);
|
||||
#endif
|
||||
|
||||
#if (!defined GPS_INT && !defined RTC_INT)
|
||||
// sync pps timer to top of second
|
||||
timerWrite(ppsIRQ, 0); // reset pps timer
|
||||
CLOCKIRQ(); // fire clock pps, this advances time 1 sec
|
||||
#endif
|
||||
|
||||
setTime(time_to_set); // set the time on top of second
|
||||
|
||||
timeSource = _lora;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was adjusted",
|
||||
millis() / 1000.0);
|
||||
} else
|
||||
ESP_LOGW(TAG, "[%0.3f] Timesync failed, outdated time calculated",
|
||||
millis() / 1000.0);
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user