Merge pull request #330 from cyberman54/development

Several small fixes from development
This commit is contained in:
Verkehrsrot 2019-03-28 23:00:13 +01:00 committed by GitHub
commit 2c1205d61a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 210 additions and 70 deletions

View File

@ -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
View 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

View File

@ -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

View File

@ -18,6 +18,7 @@ function Converter(decoded, port) {
}
if (port === 2) {
if('voltage' in converted)
converted.voltage /= 1000;
}

View File

@ -30,7 +30,13 @@ function Decoder(bytes, port) {
if (port === 2) {
// device status data
return decode(bytes, [uint16, uptime, uint8, uint32, uint8, uint8], ['voltage', 'uptime', 'cputemp', 'memory', 'reset0', 'reset1']);
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) {

View File

@ -462,8 +462,8 @@ void lora_housekeeping(void) {
// uxTaskGetStackHighWaterMark(LoraTask));
}
void user_request_network_time_callback(void *pVoidUserUTCTime,
int flagSuccess) {
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

View File

@ -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
View 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

View File

@ -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[]) {

View File

@ -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

View File

@ -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);
adjustTime(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
// 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);
// end of time critical section: release I2C bus
I2C_MUTEX_UNLOCK();
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