commit
77d1efede9
21
README.md
21
README.md
@ -31,6 +31,7 @@ This can all be done with a single small and cheap ESP32 board for less than $20
|
||||
- Heltec: LoRa-32 v1 and v2
|
||||
- TTGO: T1, T2, T3, T-Beam, T-Fox
|
||||
- Pycom: LoPy, LoPy4, FiPy
|
||||
- Radioshuttle.de: [ECO Power Board](https://www.radioshuttle.de/esp32-eco-power/esp32-eco-power-board/)
|
||||
- WeMos: LoLin32 + [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora),
|
||||
LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-Lora)
|
||||
- Adafruit ESP32 Feather + LoRa Wing + OLED Wing, #IoT Octopus32 (Octopus + ESP32 Feather)
|
||||
@ -42,9 +43,9 @@ LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-L
|
||||
- Generic ESP32
|
||||
|
||||
Depending on board hardware following features are supported:
|
||||
- LED (power/status)
|
||||
- OLED Display (detailed status)
|
||||
- RGB LED (colorized status)
|
||||
- LED (shows power & status)
|
||||
- OLED Display (shows detailed status)
|
||||
- RGB LED (shows colorized status)
|
||||
- Button
|
||||
- Silicon unique ID
|
||||
- Battery voltage monitoring
|
||||
@ -52,6 +53,7 @@ Depending on board hardware following features are supported:
|
||||
- Environmental sensor (Bosch BME280/BME680 I2C)
|
||||
- Real Time Clock (Maxim DS3231 I2C)
|
||||
- IF482 (serial) and DCF77 (gpio) time telegram generator
|
||||
- Switch external power / battery
|
||||
|
||||
Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).<br>
|
||||
Hardware dependent settings (pinout etc.) are stored in board files in /hal directory. If you want to use a ESP32 board which is not yet supported, use hal file generic.h and tailor pin mappings to your needs. Pull requests for new boards welcome.<br>
|
||||
@ -157,9 +159,14 @@ Output of sensor and peripheral data is internally switched by a bitmask registe
|
||||
| 6 | User sensor 3 |
|
||||
| 7 | reserved |
|
||||
|
||||
# Clock controller
|
||||
|
||||
Paxcounter can be used to sync a clock which has DCF77 or IF482 time telegram input with. Use case of this function is to have paxcounter hardware integrated in clocks, and use it for both counting of pax and controlling the clock. Supported external time sources are GPS time, LORAWAN network time (v1.1) and on board RTC time. Precision of the synthetic DCF77 signal depends on precision of on board available time base. Supported are both external time base (e.g. timepulse pin of GPS chip or oscillator output of RTC chip) and internal ESP32 hardware timer. Selection of time base and clock frequency is done by #defines in the board's hal file, see example in [**generic.h**](src/hal/generic.h).
|
||||
# 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/TTN/Nodered-Timeserver.json) in TTN subdirectory. Configure MQTT nodes in Node-Red to the same LORAWAN application as paxocunter device is using.
|
||||
|
||||
# Wall clock controller
|
||||
|
||||
Paxcounter can be used to sync a wall clock which has a DCF77 or IF482 time telegram input. Set *#define HAS_IF482* or *#define HAS_DCF77* in board's hal file to setup clock controller. Use case of this function is to integrate paxcounter and clock. Accurary of the synthetic DCF77 signal depends on accuracy of on board's time base, see above.
|
||||
|
||||
# Payload format
|
||||
|
||||
@ -187,8 +194,8 @@ Hereafter described is the default *plain* format, which uses MSB bit numbering.
|
||||
|
||||
**Port #1:** Paxcount data
|
||||
|
||||
byte 1-2: Number of unique pax, first seen on Wifi
|
||||
byte 3-4: Number of unique pax, first seen on Bluetooth [omited if BT disabled]
|
||||
byte 1-2: Number of unique devices, seen on Wifi
|
||||
byte 3-4: Number of unique devices, seen on Bluetooth [ommited if BT disabled]
|
||||
|
||||
**Port #2:** Device status query result
|
||||
|
||||
|
@ -43,8 +43,7 @@
|
||||
|
||||
// I2C bus access control
|
||||
#define I2C_MUTEX_LOCK() \
|
||||
xSemaphoreTake(I2Caccess, (3 * DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == \
|
||||
pdTRUE
|
||||
xSemaphoreTake(I2Caccess, pdMS_TO_TICKS(3 * DISPLAYREFRESH_MS)) == pdTRUE
|
||||
#define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess)
|
||||
|
||||
// Struct holding devices's runtime configuration
|
||||
@ -99,6 +98,7 @@ typedef struct {
|
||||
|
||||
enum sendprio_t { prio_low, prio_normal, prio_high };
|
||||
enum timesource_t { _gps, _rtc, _lora, _unsynced };
|
||||
enum mutexselect_t { no_mutex, do_mutex };
|
||||
|
||||
extern std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs;
|
||||
extern std::array<uint64_t, 0xff>::iterator it;
|
||||
@ -112,7 +112,7 @@ extern uint16_t volatile macs_total, macs_wifi, macs_ble,
|
||||
extern bool volatile TimePulseTick; // 1sec pps flag set by GPS or RTC
|
||||
extern timesource_t timeSource;
|
||||
extern hw_timer_t *displayIRQ, *ppsIRQ;
|
||||
extern SemaphoreHandle_t I2Caccess, TimePulse;
|
||||
extern SemaphoreHandle_t I2Caccess;
|
||||
extern TaskHandle_t irqHandlerTask, ClockTask;
|
||||
extern TimerHandle_t WifiChanTimer;
|
||||
extern Timezone myTZ;
|
||||
@ -123,11 +123,11 @@ extern time_t userUTCTime;
|
||||
#include "payload.h"
|
||||
#include "blescan.h"
|
||||
|
||||
#if(HAS_GPS)
|
||||
#if (HAS_GPS)
|
||||
#include "gpsread.h"
|
||||
#endif
|
||||
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
#include "lorawan.h"
|
||||
#endif
|
||||
|
||||
@ -147,7 +147,7 @@ extern time_t userUTCTime;
|
||||
#include "antenna.h"
|
||||
#endif
|
||||
|
||||
#if(HAS_SENSORS)
|
||||
#if (HAS_SENSORS)
|
||||
#include "sensor.h"
|
||||
#endif
|
||||
|
||||
|
@ -2,13 +2,10 @@
|
||||
#define _IF482_H
|
||||
|
||||
#include "globals.h"
|
||||
#include "timekeeper.h"
|
||||
|
||||
#define IF482_FRAME_SIZE (17)
|
||||
#define IF482_SYNC_FIXUP (3) // calibration to fixup processing time [milliseconds]
|
||||
|
||||
extern HardwareSerial IF482;
|
||||
|
||||
void IF482_Pulse(time_t t);
|
||||
String IRAM_ATTR IF482_Frame(time_t tt);
|
||||
|
||||
#endif
|
@ -6,6 +6,9 @@
|
||||
#define SENDCYCLE_IRQ 0x04
|
||||
#define CYCLIC_IRQ 0x08
|
||||
#define TIMESYNC_IRQ 0x10
|
||||
#define MASK_IRQ 0x20
|
||||
#define UNMASK_IRQ 0x40
|
||||
#define RESERVED_IRQ 0x80
|
||||
|
||||
#include "globals.h"
|
||||
#include "cyclic.h"
|
||||
|
@ -38,5 +38,6 @@ void rgb_set_color(uint16_t hue);
|
||||
void blink_LED(uint16_t set_color, uint16_t set_blinkduration);
|
||||
void ledLoop(void *parameter);
|
||||
void switch_LED(uint8_t state);
|
||||
void switch_LED1(uint8_t state);
|
||||
|
||||
#endif
|
@ -9,7 +9,7 @@
|
||||
extern RtcDS3231<TwoWire> Rtc; // make RTC instance globally available
|
||||
|
||||
uint8_t rtc_init(void);
|
||||
uint8_t set_rtctime(time_t t);
|
||||
uint8_t set_rtctime(time_t t, mutexselect_t mutex);
|
||||
void sync_rtctime(void);
|
||||
time_t get_rtctime(void);
|
||||
float get_rtctemp(void);
|
||||
|
@ -6,9 +6,10 @@
|
||||
#include "TimeLib.h"
|
||||
#include "irqhandler.h"
|
||||
|
||||
#if(HAS_GPS)
|
||||
#ifdef HAS_GPS
|
||||
#include "gpsread.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAS_IF482
|
||||
#include "if482.h"
|
||||
#elif defined HAS_DCF77
|
||||
|
@ -6,12 +6,9 @@
|
||||
#include "timesync.h"
|
||||
#include "timekeeper.h"
|
||||
|
||||
#define TIME_SYNC_SAMPLES 2 // 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 100 // time deviation in millisec triggering a sync
|
||||
#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length
|
||||
#define TIME_SYNC_FIXUP 0 // calibration millisec to fixup processing time
|
||||
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
|
||||
#define TIME_SYNC_FRAME_LENGTH 0x06 // timeserver answer frame length [bytes]
|
||||
#define TIME_SYNC_FIXUP 6 // calibration to fixup processing time [milliseconds]
|
||||
|
||||
void send_timesync_req(void);
|
||||
int recv_timesync_ans(uint8_t buf[], uint8_t buf_len);
|
||||
|
@ -9,6 +9,7 @@
|
||||
env_default = generic
|
||||
;env_default = ebox
|
||||
;env_default = eboxtube
|
||||
;env_default = ecopower
|
||||
;env_default = heltec
|
||||
;env_default = heltecv2
|
||||
;env_default = ttgov1
|
||||
@ -24,13 +25,13 @@ env_default = generic
|
||||
;env_default = lolin32lora
|
||||
;env_default = lolin32lite
|
||||
;env_default = octopus32
|
||||
;env_default = ebox, eboxtube, heltec, ttgobeam, lopy4, lopy, ttgov21old, ttgov21new, ttgofox
|
||||
;env_default = ecopower, eboxtube, heltec, ttgobeam, lopy4, lopy, ttgov21old, ttgov21new, ttgofox
|
||||
;
|
||||
description = Paxcounter is a proof-of-concept ESP32 device for metering passenger flows in realtime. It counts how many mobile devices are around.
|
||||
|
||||
[common]
|
||||
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
|
||||
release_version = 1.7.39
|
||||
release_version = 1.7.4
|
||||
; 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
|
||||
@ -44,7 +45,8 @@ platform_espressif32 = espressif32@1.7.0
|
||||
board_build.partitions = min_spiffs.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps_lora =
|
||||
MCCI LoRaWAN LMIC library@>=2.3.2
|
||||
;MCCI LoRaWAN LMIC library@>=2.3.2
|
||||
https://github.com/mcci-catena/arduino-lmic.git#6e5ebbe
|
||||
lib_deps_display =
|
||||
U8g2@>=2.25.7
|
||||
lib_deps_rgbled =
|
||||
@ -113,6 +115,22 @@ upload_protocol = ${common.upload_protocol}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
|
||||
[env:ecopower]
|
||||
platform = ${common.platform_espressif32}
|
||||
framework = arduino
|
||||
board = esp32dev
|
||||
board_build.partitions = ${common.board_build.partitions}
|
||||
upload_speed = 921600
|
||||
lib_deps =
|
||||
${common.lib_deps_basic}
|
||||
${common.lib_deps_lora}
|
||||
${common.lib_deps_display}
|
||||
build_flags =
|
||||
${common.build_flags_basic}
|
||||
upload_protocol = ${common.upload_protocol}
|
||||
extra_scripts = ${common.extra_scripts}
|
||||
monitor_speed = ${common.monitor_speed}
|
||||
|
||||
[env:heltec]
|
||||
platform = ${common.platform_espressif32}
|
||||
framework = arduino
|
||||
|
@ -49,7 +49,7 @@
|
||||
"to": "",
|
||||
"reg": false,
|
||||
"x": 240,
|
||||
"y": 464,
|
||||
"y": 500,
|
||||
"wires": [
|
||||
[
|
||||
"84f1cda2.069e7"
|
||||
@ -82,7 +82,7 @@
|
||||
"retain": "",
|
||||
"broker": "2a15ab6f.ab2244",
|
||||
"x": 730,
|
||||
"y": 464,
|
||||
"y": 500,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@ -135,7 +135,7 @@
|
||||
"action": "",
|
||||
"pretty": false,
|
||||
"x": 580,
|
||||
"y": 464,
|
||||
"y": 500,
|
||||
"wires": [
|
||||
[
|
||||
"72d5e7ee.d1eba8"
|
||||
@ -165,7 +165,7 @@
|
||||
"action": "",
|
||||
"property": "payload.payload_raw",
|
||||
"x": 420,
|
||||
"y": 464,
|
||||
"y": 500,
|
||||
"wires": [
|
||||
[
|
||||
"dac8aafa.389298"
|
||||
@ -212,11 +212,11 @@
|
||||
"type": "function",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Generate Time Answer",
|
||||
"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\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\nvar gateways = msg.payload.metadata.gateways;\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) return null;\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;\nvar seqno = msg.payload.payload_raw[0];\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));\nvar infoMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [infoMsg, offsetMsg, msg];",
|
||||
"outputs": 3,
|
||||
"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\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\nvar deviceMsg = { payload: msg.payload.dev_id };\nvar gateways = msg.payload.metadata.gateways;\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) return [\"n/a\", \"n/a\", deviceMsg, 0xff];\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;\nvar seqno = msg.payload.payload_raw[0];\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));\nvar euiMsg = { payload: eui };\nvar offsetMsg = { payload: offset };\n\nreturn [euiMsg, offsetMsg, deviceMsg, msg];",
|
||||
"outputs": 4,
|
||||
"noerr": 0,
|
||||
"x": 360,
|
||||
"y": 320,
|
||||
"y": 340,
|
||||
"wires": [
|
||||
[
|
||||
"37722d4b.08e3c2",
|
||||
@ -226,6 +226,9 @@
|
||||
[
|
||||
"46ce842a.614d5c"
|
||||
],
|
||||
[
|
||||
"a5dbb4ef.019168"
|
||||
],
|
||||
[
|
||||
"49e3c067.e782e"
|
||||
]
|
||||
@ -233,6 +236,7 @@
|
||||
"outputLabels": [
|
||||
"gw_eui",
|
||||
"offset_ms",
|
||||
"device",
|
||||
"time_sync_ans"
|
||||
]
|
||||
},
|
||||
@ -241,7 +245,7 @@
|
||||
"type": "debug",
|
||||
"z": "449c1517.e25f4c",
|
||||
"name": "Timeserver Gw",
|
||||
"active": true,
|
||||
"active": false,
|
||||
"tosidebar": false,
|
||||
"console": false,
|
||||
"tostatus": true,
|
||||
@ -264,7 +268,7 @@
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "col-center",
|
||||
"x": 810,
|
||||
"y": 340,
|
||||
"y": 336,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@ -290,7 +294,7 @@
|
||||
"seg1": "",
|
||||
"seg2": "",
|
||||
"x": 710,
|
||||
"y": 420,
|
||||
"y": 416,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@ -302,11 +306,11 @@
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"name": "Recent server",
|
||||
"label": "",
|
||||
"label": "Gateway",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "col-center",
|
||||
"x": 700,
|
||||
"y": 380,
|
||||
"y": 376,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
@ -318,13 +322,29 @@
|
||||
"outputs": 1,
|
||||
"noerr": 0,
|
||||
"x": 670,
|
||||
"y": 340,
|
||||
"y": 336,
|
||||
"wires": [
|
||||
[
|
||||
"8712a5ac.ed18e8"
|
||||
]
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "a5dbb4ef.019168",
|
||||
"type": "ui_text",
|
||||
"z": "449c1517.e25f4c",
|
||||
"group": "edb7cc8d.a3817",
|
||||
"order": 1,
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"name": "Recent Device",
|
||||
"label": "Device",
|
||||
"format": "{{msg.payload}}",
|
||||
"layout": "col-center",
|
||||
"x": 700,
|
||||
"y": 456,
|
||||
"wires": []
|
||||
},
|
||||
{
|
||||
"id": "2a15ab6f.ab2244",
|
||||
"type": "mqtt-broker",
|
||||
|
@ -8,7 +8,7 @@ esp_adc_cal_characteristics_t *adc_characs =
|
||||
(esp_adc_cal_characteristics_t *)calloc(
|
||||
1, sizeof(esp_adc_cal_characteristics_t));
|
||||
|
||||
static const adc1_channel_t adc_channel = HAS_BATTERY_PROBE;
|
||||
static const adc1_channel_t adc_channel = BAT_MEASURE_ADC;
|
||||
static const adc_atten_t atten = ADC_ATTEN_DB_11;
|
||||
static const adc_unit_t unit = ADC_UNIT_1;
|
||||
#endif
|
||||
@ -43,19 +43,23 @@ uint16_t read_voltage() {
|
||||
}
|
||||
adc_reading /= NO_OF_SAMPLES;
|
||||
// Convert ADC reading to voltage in mV
|
||||
uint16_t voltage =
|
||||
(uint16_t)esp_adc_cal_raw_to_voltage(adc_reading, adc_characs);
|
||||
#ifdef BATT_FACTOR
|
||||
voltage *= BATT_FACTOR;
|
||||
uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, adc_characs);
|
||||
#ifdef BAT_VOLTAGE_DIVIDER
|
||||
voltage *= BAT_VOLTAGE_DIVIDER;
|
||||
#endif
|
||||
return voltage;
|
||||
|
||||
#ifdef BAT_MEASURE_EN // turn ext. power off
|
||||
digitalWrite(EXT_POWER_SW, EXT_POWER_OFF);
|
||||
#endif
|
||||
|
||||
return (uint16_t)voltage;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool batt_sufficient() {
|
||||
#ifdef HAS_BATTERY_PROBE
|
||||
#ifdef BAT_MEASURE_ADC
|
||||
uint16_t volts = read_voltage();
|
||||
return ((volts < 1000) ||
|
||||
(volts > OTA_MIN_BATT)); // no battery or battery sufficient
|
||||
|
@ -89,7 +89,7 @@ void init_display(const char *Productname, const char *Version) {
|
||||
#endif
|
||||
|
||||
// Display chip information
|
||||
#if(VERBOSE)
|
||||
#if (VERBOSE)
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
u8x8.printf("ESP32 %d cores\nWiFi%s%s\n", chip_info.cores,
|
||||
@ -105,7 +105,7 @@ void init_display(const char *Productname, const char *Version) {
|
||||
u8x8.print(" v");
|
||||
u8x8.println(PROGVERSION);
|
||||
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
u8x8.println("DEVEUI:");
|
||||
os_getDevEui((u1_t *)buf);
|
||||
DisplayKey(buf, 8, true);
|
||||
@ -114,7 +114,7 @@ void init_display(const char *Productname, const char *Version) {
|
||||
u8x8.clear();
|
||||
u8x8.setPowerSave(!cfg.screenon); // set display off if disabled
|
||||
u8x8.draw2x2String(0, 0, "PAX:0");
|
||||
#if(BLECOUNTER)
|
||||
#if (BLECOUNTER)
|
||||
u8x8.setCursor(0, 3);
|
||||
u8x8.printf("BLTH:0");
|
||||
#endif
|
||||
@ -131,7 +131,8 @@ void refreshtheDisplay() {
|
||||
|
||||
uint8_t msgWaiting;
|
||||
char timeState, buff[16];
|
||||
const time_t t = myTZ.toLocal(now()); // note: call now() here *before* locking mutex!
|
||||
const time_t t =
|
||||
myTZ.toLocal(now()); // note: call now() here *before* locking mutex!
|
||||
|
||||
// block i2c bus access
|
||||
if (I2C_MUTEX_LOCK()) {
|
||||
@ -163,7 +164,7 @@ void refreshtheDisplay() {
|
||||
#endif
|
||||
|
||||
// update GPS status (line 2)
|
||||
#if(HAS_GPS)
|
||||
#if (HAS_GPS)
|
||||
// have we ever got valid gps data?
|
||||
if (gps.passedChecksum() > 0) {
|
||||
u8x8.setCursor(9, 2);
|
||||
@ -178,7 +179,7 @@ void refreshtheDisplay() {
|
||||
#endif
|
||||
|
||||
// update bluetooth counter + LoRa SF (line 3)
|
||||
#if(BLECOUNTER)
|
||||
#if (BLECOUNTER)
|
||||
u8x8.setCursor(0, 3);
|
||||
if (cfg.blescan)
|
||||
u8x8.printf("BLTH:%-5d", macs_ble);
|
||||
@ -186,7 +187,7 @@ void refreshtheDisplay() {
|
||||
u8x8.printf("%s", "BLTH:off");
|
||||
#endif
|
||||
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
u8x8.setCursor(11, 3);
|
||||
u8x8.printf("SF:");
|
||||
if (cfg.adrmode) // if ADR=on then display SF value inverse
|
||||
@ -197,35 +198,49 @@ void refreshtheDisplay() {
|
||||
u8x8.setInverseFont(0);
|
||||
#endif // HAS_LORA
|
||||
|
||||
// update wifi counter + channel display (line 4)
|
||||
// line 4: update wifi counter + channel display
|
||||
u8x8.setCursor(0, 4);
|
||||
u8x8.printf("WIFI:%-5d", macs_wifi);
|
||||
u8x8.setCursor(11, 4);
|
||||
u8x8.printf("ch:%02d", channel);
|
||||
|
||||
// update RSSI limiter status & free memory display (line 5)
|
||||
// line 5: update RSSI limiter status & free memory display
|
||||
u8x8.setCursor(0, 5);
|
||||
u8x8.printf(!cfg.rssilimit ? "RLIM:off " : "RLIM:%-4d", cfg.rssilimit);
|
||||
u8x8.setCursor(10, 5);
|
||||
u8x8.printf("%4dKB", getFreeRAM() / 1024);
|
||||
|
||||
#if(HAS_LORA)
|
||||
// line 6: update time-of-day or LoRa status display
|
||||
u8x8.setCursor(0, 6);
|
||||
#if (!defined HAS_DCF77) && (!defined HAS_IF482)
|
||||
// update LoRa status display (line 6)
|
||||
u8x8.printf("%-16s", display_line6);
|
||||
#else // we want a systime display instead LoRa status
|
||||
#if (TIME_SYNC_INTERVAL)
|
||||
// we want a systime display instead LoRa status
|
||||
timeState = TimePulseTick ? ' ' : timeSetSymbols[timeSource];
|
||||
TimePulseTick = false;
|
||||
// display inverse timeState if clock controller is enabled
|
||||
#if (defined HAS_DCF77) || (defined HAS_IF482)
|
||||
u8x8.printf("%02d:%02d:%02d", hour(t), minute(t), second(t), timeState);
|
||||
u8x8.setInverseFont(1);
|
||||
u8x8.printf("%c", timeState);
|
||||
u8x8.setInverseFont(0);
|
||||
u8x8.printf(" %2d.%3s", day(t), printmonth[month(t)]);
|
||||
#else
|
||||
u8x8.printf("%02d:%02d:%02d%c %2d.%3s", hour(t), minute(t), second(t),
|
||||
timeState, day(t), printmonth[month(t)]);
|
||||
#endif // HAS_DCF77 || HAS_IF482
|
||||
|
||||
#else // update LoRa status display
|
||||
#if (HAS_LORA)
|
||||
u8x8.printf("%-16s", display_line6);
|
||||
#endif
|
||||
|
||||
// update LMiC event display (line 7)
|
||||
#endif // TIME_SYNC_INTERVAL
|
||||
|
||||
#if (HAS_LORA)
|
||||
// line 7: update LMiC event display
|
||||
u8x8.setCursor(0, 7);
|
||||
u8x8.printf("%-14s", display_line7);
|
||||
|
||||
// update LoRa send queue display (line 7)
|
||||
// update LoRa send queue display
|
||||
msgWaiting = uxQueueMessagesWaiting(LoraSendQueue);
|
||||
if (msgWaiting) {
|
||||
sprintf(buff, "%2d", msgWaiting);
|
||||
|
55
src/hal/ecopower.h
Normal file
55
src/hal/ecopower.h
Normal file
@ -0,0 +1,55 @@
|
||||
// clang-format off
|
||||
|
||||
#ifndef _GENERIC_H
|
||||
#define _GENERIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define HAS_LORA 1 // comment out if device shall not send data via LoRa or has no LoRa
|
||||
#define CFG_sx1276_radio 1 // select LoRa chip
|
||||
|
||||
//#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature
|
||||
|
||||
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||
//#define DISPLAY_FLIP 1 // use if display is rotated
|
||||
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BAT_VOLTAGE_DIVIDER ((82.0+220.0)/82.0) // 82k + 220k 1%
|
||||
#define BAT_MEASURE_EN EXT_POWER_SW // Turn power on for messurement
|
||||
|
||||
#define EXT_POWER_SW 15 // Switch VDD on pin JP10
|
||||
#define EXT_POWER_ON 0
|
||||
#define EXT_POWER_OFF 1
|
||||
|
||||
#define HAS_LED (2) // on board green LED
|
||||
#define HAS_TWO_LED (12) // on board red LED
|
||||
//#define HAS_BUTTON (0) // on board button -> don't use, is same as RTC_INT!
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
#define MY_OLED_SDA (21)
|
||||
#define MY_OLED_SCL (22)
|
||||
#define MY_OLED_RST U8X8_PIN_NONE
|
||||
|
||||
// Settings for on board DS3231 RTC chip
|
||||
// note: to use RTC_INT, capacitor 100nF next to red LED must be removed to sharpen interrupt signal slope
|
||||
// and setting EXT_POWER_ON is needed (this is done in main.cpp)
|
||||
#define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL
|
||||
#define RTC_INT GPIO_NUM_0 //
|
||||
|
||||
// Settings for IF482 interface
|
||||
//#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_3, GPIO_NUM_1 // RX, TX
|
||||
|
||||
// Settings for DCF77 interface
|
||||
//#define HAS_DCF77 GPIO_NUM_14 // JP8 #13
|
||||
//#define DCF77_ACTIVE_LOW 1
|
||||
|
||||
// Pins for LORA chip SPI interface, reset line and interrupt lines
|
||||
#define LORA_SCK (18)
|
||||
#define LORA_CS (5)
|
||||
#define LORA_MISO (19)
|
||||
#define LORA_MOSI (23)
|
||||
#define LORA_RST (17)
|
||||
#define LORA_IRQ (16)
|
||||
#define LORA_IO1 (14) // JP8 #13 to be external wired
|
||||
#define LORA_IO2 LMIC_UNUSED_PIN
|
||||
|
||||
#endif
|
@ -35,8 +35,8 @@
|
||||
|
||||
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||
//#define DISPLAY_FLIP 1 // use if display is rotated
|
||||
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BATT_FACTOR 2 // voltage divider 100k/100k on board
|
||||
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board
|
||||
|
||||
#define HAS_LED (21) // on board LED
|
||||
#define HAS_BUTTON (39) // on board button
|
||||
|
@ -18,9 +18,11 @@
|
||||
#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
|
||||
//#define BAT_MEASURE_ADC ADC2_GPIO13_CHANNEL // battery probe GPIO pin
|
||||
//#define BAT_VOLTAGE_DIVIDER 4 // voltage divider 220k/100k on board
|
||||
//#define EXT_POWER_SW GPIO_NUM_21 // switches battery power, Vext control 0 = on / 1 = off
|
||||
//#define EXT_POWER_ON 0
|
||||
//#define EXT_POWER_OFF 1
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
#define MY_OLED_SDA (4)
|
||||
|
@ -28,8 +28,8 @@
|
||||
//#define LED_ACTIVE_LOW 1 // use if LoPy is on Expansion Board, this has a user LED
|
||||
//#define HAS_BUTTON (13) // user button on expansion board
|
||||
//#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
|
||||
//#define HAS_BATTERY_PROBE ADC1_GPIO39_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
//#define BATT_FACTOR 2 // voltage divider 1MOhm/1MOhm -> expansion board 3.0
|
||||
//#define BATT_FACTOR 4 // voltage divider 115kOhm/56kOhm -> expansion board 2.0
|
||||
//#define BAT_MEASURE_ADC ADC1_GPIO39_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
//#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 1MOhm/1MOhm -> expansion board 3.0
|
||||
//#define BAT_VOLTAGE_DIVIDER 4 // voltage divider 115kOhm/56kOhm -> expansion board 2.0
|
||||
|
||||
#endif
|
||||
|
@ -38,8 +38,8 @@
|
||||
#define LED_ACTIVE_LOW 1 // use if LoPy is on Expansion Board, this has a user LED
|
||||
#define HAS_BUTTON (13) // user button on expansion board
|
||||
#define BUTTON_PULLUP 1 // Button need pullup instead of default pulldown
|
||||
#define HAS_BATTERY_PROBE ADC1_GPIO39_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BATT_FACTOR 2 // voltage divider 1MOhm/1MOhm -> expansion board 3.0
|
||||
//#define BATT_FACTOR 4 // voltage divider 115kOhm/56kOhm -> expansion board 2.0
|
||||
#define BAT_MEASURE_ADC ADC1_GPIO39_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 1MOhm/1MOhm -> expansion board 3.0
|
||||
//#define BAT_VOLTAGE_DIVIDER 4 // voltage divider 115kOhm/56kOhm -> expansion board 2.0
|
||||
|
||||
#endif
|
@ -17,8 +17,8 @@
|
||||
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
|
||||
#define BOARD_HAS_PSRAM // use extra 4MB external RAM
|
||||
#define HAS_BUTTON GPIO_NUM_39 // on board button (next to reset)
|
||||
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BATT_FACTOR 2 // voltage divider 100k/100k on board
|
||||
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board
|
||||
|
||||
// GPS settings
|
||||
#define HAS_GPS 1 // use on board GPS
|
||||
|
@ -10,9 +10,12 @@
|
||||
|
||||
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||
#define HAS_LED NOT_A_PIN // green on board LED is useless, is GPIO25, which switches power for Lora+Display
|
||||
//#define HAS_LOWPOWER_SWITCH GPIO_NUM_25 // switches power for LoRa chip + display (0 = off / 1 = on)
|
||||
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL
|
||||
#define BATT_FACTOR 2 // voltage divider 100k/100k on board
|
||||
|
||||
//#define EXT_POWER_SW GPIO_NUM_25 // switches power for LoRa chip + display (0 = off / 1 = on)
|
||||
//#define EXT_POWER_ON 1
|
||||
//#define EXT_POWER_OFF 0
|
||||
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL
|
||||
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board
|
||||
#define HAS_BUTTON GPIO_NUM_36 // on board button (next to reset)
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
@ -25,7 +28,7 @@
|
||||
#define RTC_INT GPIO_NUM_34 // timepulse with accuracy +/- 2*e-6 [microseconds] = 0,1728sec / day
|
||||
|
||||
// Settings for IF482 interface
|
||||
//#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters
|
||||
#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters
|
||||
|
||||
// Settings for DCF77 interface
|
||||
//#define HAS_DCF77 GPIO_NUM_14
|
||||
|
@ -20,8 +20,8 @@
|
||||
|
||||
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||
#define HAS_LED (25) // green on board LED
|
||||
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BATT_FACTOR 2 // voltage divider 100k/100k on board
|
||||
#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
#define MY_OLED_SDA (21)
|
||||
|
@ -18,8 +18,8 @@
|
||||
|
||||
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
|
||||
//#define DISPLAY_FLIP 1 // rotated display
|
||||
//#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
//#define BATT_FACTOR 2 // voltage divider 100k/100k on board
|
||||
//#define BAT_MEASURE_ADC ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
|
||||
//#define BAT_VOLTAGE_DIVIDER 2 // voltage divider 100k/100k on board
|
||||
|
||||
// Pins for I2C interface of OLED Display
|
||||
#define MY_OLED_SDA (21)
|
||||
|
@ -84,18 +84,6 @@ not evaluated by model BU-190, use "F" instead for this model
|
||||
// Local logging tag
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
|
||||
|
||||
// triggered by timepulse to send IF482 signal
|
||||
void IF482_Pulse(time_t t) {
|
||||
|
||||
static const TickType_t txDelay =
|
||||
pdMS_TO_TICKS(1000) - tx_Ticks(IF482_FRAME_SIZE, HAS_IF482);
|
||||
|
||||
vTaskDelay(txDelay); // wait until moment to fire
|
||||
IF482.print(IF482_Frame(t + 1)); // note: if482 telegram for *next* second
|
||||
}
|
||||
|
||||
String IRAM_ATTR IF482_Frame(time_t printTime) {
|
||||
|
||||
time_t t = myTZ.toLocal(printTime);
|
||||
|
@ -9,6 +9,7 @@ void irqHandler(void *pvParameters) {
|
||||
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
|
||||
|
||||
uint32_t InterruptStatus;
|
||||
static bool mask_irq = false;
|
||||
|
||||
// task remains in blocked state until it is notified by an irq
|
||||
for (;;) {
|
||||
@ -17,6 +18,16 @@ void irqHandler(void *pvParameters) {
|
||||
&InterruptStatus, // Receives the notification value
|
||||
portMAX_DELAY); // wait forever
|
||||
|
||||
// interrupt handler to be enabled?
|
||||
if (InterruptStatus & UNMASK_IRQ)
|
||||
mask_irq = false;
|
||||
else if (mask_irq)
|
||||
continue; // suppress processing if interrupt handler is disabled
|
||||
|
||||
// interrupt handler to be disabled?
|
||||
if (InterruptStatus & MASK_IRQ)
|
||||
mask_irq = true;
|
||||
|
||||
// button pressed?
|
||||
#ifdef HAS_BUTTON
|
||||
if (InterruptStatus & BUTTON_IRQ)
|
||||
@ -25,7 +36,7 @@ void irqHandler(void *pvParameters) {
|
||||
|
||||
// display needs refresh?
|
||||
#ifdef HAS_DISPLAY
|
||||
if (InterruptStatus & DISPLAY_IRQ)
|
||||
if (InterruptStatus & DISPLAY_IRQ)
|
||||
refreshtheDisplay();
|
||||
#endif
|
||||
|
||||
@ -54,12 +65,10 @@ void irqHandler(void *pvParameters) {
|
||||
|
||||
#ifdef HAS_DISPLAY
|
||||
void IRAM_ATTR DisplayIRQ() {
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
xHigherPriorityTaskWoken = pdFALSE;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
xTaskNotifyFromISR(irqHandlerTask, DISPLAY_IRQ, eSetBits,
|
||||
&xHigherPriorityTaskWoken);
|
||||
|
||||
if (xHigherPriorityTaskWoken)
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
@ -67,8 +76,7 @@ void IRAM_ATTR DisplayIRQ() {
|
||||
|
||||
#ifdef HAS_BUTTON
|
||||
void IRAM_ATTR ButtonIRQ() {
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
xHigherPriorityTaskWoken = pdFALSE;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
xTaskNotifyFromISR(irqHandlerTask, BUTTON_IRQ, eSetBits,
|
||||
&xHigherPriorityTaskWoken);
|
||||
|
22
src/led.cpp
22
src/led.cpp
@ -107,6 +107,26 @@ void switch_LED(uint8_t state) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void switch_LED1(uint8_t state) {
|
||||
#ifdef HAS_TWO_LED
|
||||
if (state == LED_ON) {
|
||||
// switch LED on
|
||||
#ifdef LED1_ACTIVE_LOW
|
||||
digitalWrite(HAS_TWO_LED, LOW);
|
||||
#else
|
||||
digitalWrite(HAS_TWO_LED, HIGH);
|
||||
#endif
|
||||
} else if (state == LED_OFF) {
|
||||
// switch LED off
|
||||
#ifdef LED1_ACTIVE_LOW
|
||||
digitalWrite(HAS_TWO_LED, HIGH);
|
||||
#else
|
||||
digitalWrite(HAS_TWO_LED, LOW);
|
||||
#endif
|
||||
}
|
||||
#endif // HAS_TWO_LED
|
||||
}
|
||||
|
||||
void blink_LED(uint16_t set_color, uint16_t set_blinkduration) {
|
||||
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
|
||||
LEDColor = set_color; // set color for RGB LED
|
||||
@ -137,7 +157,7 @@ void ledLoop(void *parameter) {
|
||||
// No custom blink, check LoRaWAN state
|
||||
} else {
|
||||
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
// LED indicators for viusalizing LoRaWAN state
|
||||
if (LMIC.opmode & (OP_JOINING | OP_REJOIN)) {
|
||||
LEDColor = COLOR_YELLOW;
|
||||
|
@ -19,7 +19,7 @@
|
||||
// LMIC LORAWAN STACK SETTINGS
|
||||
// --> adapt to your device only if necessary
|
||||
|
||||
//#define LMIC_USE_INTERRUPTS
|
||||
//#define LMIC_USE_INTERRUPTS 1
|
||||
|
||||
//time sync via LoRaWAN network, is not yet supported by TTN (LoRaWAN spec v1.0.3)
|
||||
#define LMIC_ENABLE_DeviceTimeReq 1
|
||||
@ -35,7 +35,7 @@
|
||||
// so consuming more power. You may sharpen (reduce) this value if you are
|
||||
// limited on battery.
|
||||
// ATTN: VALUES > 7 WILL CAUSE RECEPTION AND JOIN PROBLEMS WITH HIGH SF RATES
|
||||
#define CLOCK_ERROR_PROCENTAGE 3
|
||||
#define CLOCK_ERROR_PROCENTAGE 5
|
||||
|
||||
// Set this to 1 to enable some basic debug output (using printf) about
|
||||
// RF settings used during transmission and reception. Set to 2 to
|
||||
|
@ -182,19 +182,19 @@ void onEvent(ev_t ev) {
|
||||
switch (ev) {
|
||||
|
||||
case EV_SCAN_TIMEOUT:
|
||||
strcpy_P(buff, PSTR("SCAN_TIMEOUT"));
|
||||
strcpy_P(buff, PSTR("SCAN TIMEOUT"));
|
||||
break;
|
||||
|
||||
case EV_BEACON_FOUND:
|
||||
strcpy_P(buff, PSTR("BEACON_FOUND"));
|
||||
strcpy_P(buff, PSTR("BEACON FOUND"));
|
||||
break;
|
||||
|
||||
case EV_BEACON_MISSED:
|
||||
strcpy_P(buff, PSTR("BEACON_MISSED"));
|
||||
strcpy_P(buff, PSTR("BEACON MISSED"));
|
||||
break;
|
||||
|
||||
case EV_BEACON_TRACKED:
|
||||
strcpy_P(buff, PSTR("BEACON_TRACKED"));
|
||||
strcpy_P(buff, PSTR("BEACON TRACKED"));
|
||||
break;
|
||||
|
||||
case EV_JOINING:
|
||||
@ -219,12 +219,16 @@ void onEvent(ev_t ev) {
|
||||
cfg.txpower);
|
||||
break;
|
||||
|
||||
case EV_RFU1:
|
||||
strcpy_P(buff, PSTR("RFU1"));
|
||||
break;
|
||||
|
||||
case EV_JOIN_FAILED:
|
||||
strcpy_P(buff, PSTR("JOIN_FAILED"));
|
||||
strcpy_P(buff, PSTR("JOIN FAILED"));
|
||||
break;
|
||||
|
||||
case EV_REJOIN_FAILED:
|
||||
strcpy_P(buff, PSTR("REJOIN_FAILED"));
|
||||
strcpy_P(buff, PSTR("REJOIN FAILED"));
|
||||
break;
|
||||
|
||||
case EV_TXCOMPLETE:
|
||||
@ -236,8 +240,8 @@ void onEvent(ev_t ev) {
|
||||
}
|
||||
#endif
|
||||
|
||||
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED_ACK")
|
||||
: PSTR("TX_COMPLETE"));
|
||||
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK")
|
||||
: PSTR("TX COMPLETE"));
|
||||
sprintf(display_line6, " "); // clear previous lmic status
|
||||
|
||||
if (LMIC.dataLen) { // did we receive payload data -> display info
|
||||
@ -265,7 +269,7 @@ void onEvent(ev_t ev) {
|
||||
break;
|
||||
|
||||
case EV_LOST_TSYNC:
|
||||
strcpy_P(buff, PSTR("LOST_TSYNC"));
|
||||
strcpy_P(buff, PSTR("LOST TSYNC"));
|
||||
break;
|
||||
|
||||
case EV_RESET:
|
||||
@ -274,38 +278,46 @@ void onEvent(ev_t ev) {
|
||||
|
||||
case EV_RXCOMPLETE:
|
||||
// data received in ping slot
|
||||
strcpy_P(buff, PSTR("RX_COMPLETE"));
|
||||
strcpy_P(buff, PSTR("RX COMPLETE"));
|
||||
break;
|
||||
|
||||
case EV_LINK_DEAD:
|
||||
strcpy_P(buff, PSTR("LINK_DEAD"));
|
||||
strcpy_P(buff, PSTR("LINK DEAD"));
|
||||
break;
|
||||
|
||||
case EV_LINK_ALIVE:
|
||||
strcpy_P(buff, PSTR("LINK_ALIVE"));
|
||||
break;
|
||||
|
||||
case EV_SCAN_FOUND:
|
||||
strcpy_P(buff, PSTR("SCAN FOUND"));
|
||||
break;
|
||||
|
||||
case EV_TXSTART:
|
||||
if (!(LMIC.opmode & OP_JOINING))
|
||||
strcpy_P(buff, PSTR("TX_START"));
|
||||
strcpy_P(buff, PSTR("TX START"));
|
||||
break;
|
||||
|
||||
case EV_SCAN_FOUND:
|
||||
strcpy_P(buff, PSTR("SCAN_FOUND"));
|
||||
case EV_TXCANCELED:
|
||||
strcpy_P(buff, PSTR("TX CANCELLED"));
|
||||
break;
|
||||
|
||||
case EV_RFU1:
|
||||
strcpy_P(buff, PSTR("RFU1"));
|
||||
case EV_RXSTART:
|
||||
strcpy_P(buff, PSTR("RX START"));
|
||||
break;
|
||||
|
||||
case EV_JOIN_TXCOMPLETE:
|
||||
strcpy_P(buff, PSTR("JOIN WAIT"));
|
||||
break;
|
||||
|
||||
default:
|
||||
sprintf_P(buff, PSTR("UNKNOWN_EVENT_%d"), ev);
|
||||
sprintf_P(buff, PSTR("LMIC EV %d"), ev);
|
||||
break;
|
||||
}
|
||||
|
||||
// Log & Display if asked
|
||||
if (*buff) {
|
||||
ESP_LOGI(TAG, "EV_%s", buff);
|
||||
ESP_LOGI(TAG, "%s", buff);
|
||||
sprintf(display_line7, buff);
|
||||
}
|
||||
}
|
||||
@ -491,7 +503,7 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
|
||||
if (timeIsValid(*pUserUTCTime)) {
|
||||
setTime(*pUserUTCTime);
|
||||
#ifdef HAS_RTC
|
||||
set_rtctime(*pUserUTCTime); // calibrate RTC if we have one
|
||||
set_rtctime(*pUserUTCTime, do_mutex); // calibrate RTC if we have one
|
||||
#endif
|
||||
timeSource = _lora;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat
|
||||
|
60
src/main.cpp
60
src/main.cpp
@ -36,7 +36,7 @@ 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
|
||||
bmeloop 1 1 reads data from BME sensor via i2c
|
||||
timesync_req 1 4 temporary task for processing time sync requests
|
||||
timesync_req 1 2 temporary task for processing time sync requests
|
||||
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
|
||||
|
||||
Low priority numbers denote low priority tasks.
|
||||
@ -46,9 +46,9 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore
|
||||
|
||||
// ESP32 hardware timers
|
||||
-------------------------------------------------------------------------------
|
||||
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS in paxcounter.conf)
|
||||
1 ppsIRQ -> pps clock irq -> 1sec
|
||||
2 unused
|
||||
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
|
||||
1 ppsIRQ -> pps clock irq -> 1sec
|
||||
2 unused
|
||||
3 unused
|
||||
|
||||
|
||||
@ -56,14 +56,14 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
fired by hardware
|
||||
DisplayIRQ -> esp32 timer 0 -> irqhandler.cpp
|
||||
CLOCKIRQ -> esp32 timer 1 -> timekeeper.cpp
|
||||
ButtonIRQ -> external gpio -> irqhandler.cpp
|
||||
DisplayIRQ -> esp32 timer 0 -> irqHandlerTask (Core 1)
|
||||
CLOCKIRQ -> esp32 timer 1 -> ClockTask (Core 1)
|
||||
ButtonIRQ -> external gpio -> irqHandlerTask (Core 1)
|
||||
|
||||
fired by software (Ticker.h)
|
||||
TIMESYNC_IRQ -> timeSync() -> timerkeeper.cpp
|
||||
CYLCIC_IRQ -> housekeeping() -> cyclic.cpp
|
||||
SENDCYCLE_IRQ -> sendcycle() -> senddata.cpp
|
||||
TIMESYNC_IRQ -> timeSync() -> irqHandlerTask (Core 1)
|
||||
CYLCIC_IRQ -> housekeeping() -> irqHandlerTask (Core 1)
|
||||
SENDCYCLE_IRQ -> sendcycle() -> irqHandlerTask (Core 1)
|
||||
|
||||
|
||||
// External RTC timer (if present)
|
||||
@ -84,7 +84,7 @@ uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
|
||||
hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL;
|
||||
|
||||
TaskHandle_t irqHandlerTask, ClockTask;
|
||||
SemaphoreHandle_t I2Caccess, TimePulse;
|
||||
SemaphoreHandle_t I2Caccess;
|
||||
bool volatile TimePulseTick = false;
|
||||
time_t userUTCTime = 0;
|
||||
timesource_t timeSource = _unsynced;
|
||||
@ -113,9 +113,7 @@ void setup() {
|
||||
if (I2Caccess)
|
||||
xSemaphoreGive(I2Caccess); // Flag the i2c bus available for use
|
||||
|
||||
TimePulse = xSemaphoreCreateBinary(); // as signal that shows time pulse flip
|
||||
|
||||
// disable brownout detection
|
||||
// disable brownout detection
|
||||
#ifdef DISABLE_BROWNOUT
|
||||
// register with brownout is at address DR_REG_RTCCNTL_BASE + 0xd4
|
||||
(*((uint32_t volatile *)ETS_UNCACHED_ADDR((DR_REG_RTCCNTL_BASE + 0xd4)))) = 0;
|
||||
@ -156,7 +154,7 @@ void setup() {
|
||||
ESP.getFlashChipSpeed());
|
||||
ESP_LOGI(TAG, "Wifi/BT software coexist version %s", esp_coex_version_get());
|
||||
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
ESP_LOGI(TAG, "IBM LMIC version %d.%d.%d", LMIC_VERSION_MAJOR,
|
||||
LMIC_VERSION_MINOR, LMIC_VERSION_BUILD);
|
||||
ESP_LOGI(TAG, "Arduino LMIC version %d.%d.%d.%d",
|
||||
@ -167,7 +165,7 @@ void setup() {
|
||||
showLoraKeys();
|
||||
#endif // HAS_LORA
|
||||
|
||||
#if(HAS_GPS)
|
||||
#if (HAS_GPS)
|
||||
ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion());
|
||||
#endif
|
||||
|
||||
@ -182,18 +180,26 @@ void setup() {
|
||||
strcat_P(features, " PSRAM");
|
||||
#endif
|
||||
|
||||
// set low power mode to off
|
||||
#ifdef HAS_LOWPOWER_SWITCH
|
||||
pinMode(HAS_LOWPOWER_SWITCH, OUTPUT);
|
||||
digitalWrite(HAS_LOWPOWER_SWITCH, LOW);
|
||||
strcat_P(features, " LPWR");
|
||||
// set external power mode
|
||||
#ifdef EXT_POWER_SW
|
||||
pinMode(EXT_POWER_SW, OUTPUT);
|
||||
digitalWrite(EXT_POWER_SW, EXT_POWER_ON);
|
||||
strcat_P(features, " VEXT");
|
||||
#endif
|
||||
|
||||
#ifdef BAT_MEASURE_EN
|
||||
pinMode(BAT_MEASURE_EN, OUTPUT);
|
||||
#endif
|
||||
|
||||
// initialize leds
|
||||
#if (HAS_LED != NOT_A_PIN)
|
||||
pinMode(HAS_LED, OUTPUT);
|
||||
strcat_P(features, " LED");
|
||||
// switch on power LED if we have 2 LEDs, else use it for status
|
||||
#ifdef HAS_TWO_LED
|
||||
pinMode(HAS_TWO_LED, OUTPUT);
|
||||
strcat_P(features, " LED1");
|
||||
#endif
|
||||
// use LED for power display if we have additional RGB LED, else for status
|
||||
#ifdef HAS_RGB_LED
|
||||
switch_LED(LED_ON);
|
||||
strcat_P(features, " RGB");
|
||||
@ -221,7 +227,7 @@ void setup() {
|
||||
#endif
|
||||
|
||||
// initialize battery status
|
||||
#ifdef HAS_BATTERY_PROBE
|
||||
#ifdef BAT_MEASURE_ADC
|
||||
strcat_P(features, " BATT");
|
||||
calibrate_voltage();
|
||||
batt_voltage = read_voltage();
|
||||
@ -269,7 +275,7 @@ void setup() {
|
||||
#endif // HAS_BUTTON
|
||||
|
||||
// initialize gps
|
||||
#if(HAS_GPS)
|
||||
#if (HAS_GPS)
|
||||
strcat_P(features, " GPS");
|
||||
if (gps_init()) {
|
||||
ESP_LOGI(TAG, "Starting GPS Feed...");
|
||||
@ -284,13 +290,13 @@ void setup() {
|
||||
#endif
|
||||
|
||||
// initialize sensors
|
||||
#if(HAS_SENSORS)
|
||||
#if (HAS_SENSORS)
|
||||
strcat_P(features, " SENS");
|
||||
sensor_init();
|
||||
#endif
|
||||
|
||||
// initialize LoRa
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
strcat_P(features, " LORA");
|
||||
assert(lora_stack_init() == ESP_OK);
|
||||
#endif
|
||||
@ -425,7 +431,7 @@ void setup() {
|
||||
|
||||
void loop() {
|
||||
while (1) {
|
||||
#if(HAS_LORA)
|
||||
#if (HAS_LORA)
|
||||
os_runloop_once(); // execute lmic scheduled jobs and events
|
||||
#endif
|
||||
delay(2); // yield to CPU
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "mbedtls/aes.h"
|
||||
#include "lmic/oslmic.h"
|
||||
|
||||
#if defined(USE_MBEDTLS_AES)
|
||||
#if defined USE_MBEDTLS_AES
|
||||
|
||||
void lmic_aes_encrypt(u1_t *data, u1_t *key)
|
||||
{
|
||||
|
@ -1,4 +1,4 @@
|
||||
#ifdef USE_OTA
|
||||
#if (USE_OTA)
|
||||
|
||||
/*
|
||||
Parts of this code:
|
||||
|
@ -66,10 +66,16 @@
|
||||
#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 0 // sync time attempt each .. minutes from time source (GPS/LORA/RTC) [default = 60], 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]
|
||||
#define TIME_SYNC_TIMESERVER 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_CYCLE 20 // delay between two time samples [seconds]
|
||||
#define TIME_SYNC_TIMEOUT 120 // timeout waiting for timeserver answer [seconds]
|
||||
|
||||
// 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
|
||||
#define STANDARD_TIME {"CET ", Last, Sun, Oct, 3, 60} // Central European Standard Time
|
||||
|
@ -373,7 +373,7 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius,
|
||||
buffer[cursor++] = LPP_ANALOG_INPUT;
|
||||
buffer[cursor++] = highByte(volt);
|
||||
buffer[cursor++] = lowByte(volt);
|
||||
#endif // HAS_BATTERY_PROBE
|
||||
#endif // BAT_MEASURE_ADC
|
||||
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_TEMPERATURE_CHANNEL;
|
||||
|
@ -24,17 +24,17 @@ uint8_t rtc_init(void) {
|
||||
Rtc.SetIsRunning(true);
|
||||
}
|
||||
|
||||
// If you want to initialize a fresh RTC to compiled time, use this code
|
||||
/*
|
||||
RtcDateTime tt = Rtc.GetDateTime();
|
||||
time_t t = tt.Epoch32Time(); // sec2000 -> epoch
|
||||
#if (TIME_SYNC_COMPILEDATE)
|
||||
// initialize a blank RTC without battery backup with compiled time
|
||||
RtcDateTime tt = Rtc.GetDateTime();
|
||||
time_t t = tt.Epoch32Time(); // sec2000 -> epoch
|
||||
|
||||
if (!Rtc.IsDateTimeValid() || !timeIsValid(t)) {
|
||||
ESP_LOGW(TAG, "RTC has no recent time, setting to compilation date");
|
||||
Rtc.SetDateTime(
|
||||
RtcDateTime(compiledUTC() - SECS_YR_2000)); // epoch -> sec2000
|
||||
}
|
||||
*/
|
||||
if (!Rtc.IsDateTimeValid() || !timeIsValid(t)) {
|
||||
ESP_LOGW(TAG, "RTC has no recent time, setting to compiled time");
|
||||
Rtc.SetDateTime(
|
||||
RtcDateTime(compiledUTC() - SECS_YR_2000)); // epoch -> sec2000
|
||||
}
|
||||
#endif
|
||||
|
||||
I2C_MUTEX_UNLOCK(); // release i2c bus access
|
||||
ESP_LOGI(TAG, "RTC initialized");
|
||||
@ -46,10 +46,15 @@ uint8_t rtc_init(void) {
|
||||
|
||||
} // rtc_init()
|
||||
|
||||
uint8_t set_rtctime(time_t t) { // t is UTC in seconds epoch time
|
||||
if (I2C_MUTEX_LOCK()) {
|
||||
uint8_t set_rtctime(time_t t, mutexselect_t mutex) { // t is sec epoch time
|
||||
if (!mutex || I2C_MUTEX_LOCK()) {
|
||||
#ifdef RTC_INT // sync rtc 1Hz pulse on top of second
|
||||
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); // off
|
||||
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); // start
|
||||
#endif
|
||||
Rtc.SetDateTime(RtcDateTime(t - SECS_YR_2000)); // epoch -> sec2000
|
||||
I2C_MUTEX_UNLOCK();
|
||||
if (mutex)
|
||||
I2C_MUTEX_UNLOCK();
|
||||
ESP_LOGI(TAG, "RTC time synced");
|
||||
return 1; // success
|
||||
} else {
|
||||
|
@ -14,6 +14,10 @@ static const char TAG[] = __FILE__;
|
||||
// symbol to display current time source
|
||||
const char timeSetSymbols[] = {'G', 'R', 'L', '?'};
|
||||
|
||||
#ifdef HAS_IF482
|
||||
HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
|
||||
#endif
|
||||
|
||||
Ticker timesyncer;
|
||||
|
||||
void timeSync() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); }
|
||||
@ -26,7 +30,7 @@ time_t timeProvider(void) {
|
||||
t = get_gpstime(); // fetch recent time from last NEMA record
|
||||
if (t) {
|
||||
#ifdef HAS_RTC
|
||||
set_rtctime(t); // calibrate RTC
|
||||
set_rtctime(t, do_mutex); // calibrate RTC
|
||||
#endif
|
||||
timeSource = _gps;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat
|
||||
@ -115,17 +119,18 @@ void timepulse_start(void) {
|
||||
// interrupt service routine triggered by either pps or esp32 hardware timer
|
||||
void IRAM_ATTR CLOCKIRQ(void) {
|
||||
|
||||
BaseType_t xHigherPriorityTaskWoken;
|
||||
SyncToPPS(); // calibrates UTC systime, see microTime.h
|
||||
xHigherPriorityTaskWoken = pdFALSE;
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
SyncToPPS(); // calibrates UTC systime and advances it +1, see microTime.h
|
||||
|
||||
if (ClockTask != NULL)
|
||||
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
||||
&xHigherPriorityTaskWoken);
|
||||
|
||||
#if defined GPS_INT || defined RTC_INT
|
||||
xSemaphoreGiveFromISR(TimePulse, &xHigherPriorityTaskWoken);
|
||||
TimePulseTick = !TimePulseTick; // flip ticker
|
||||
#ifdef HAS_DISPLAY
|
||||
#if (defined GPS_INT || defined RTC_INT)
|
||||
TimePulseTick = !TimePulseTick; // flip pulse ticker
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// yield only if we should
|
||||
@ -133,6 +138,7 @@ void IRAM_ATTR CLOCKIRQ(void) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
||||
|
||||
// helper function to check plausibility of a time
|
||||
time_t timeIsValid(time_t const t) {
|
||||
// is it a time in the past? we use compile date to guess
|
||||
@ -164,8 +170,8 @@ TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
|
||||
|
||||
uint32_t databits = ((config & 0x0c) >> 2) + 5;
|
||||
uint32_t stopbits = ((config & 0x20) >> 5) + 1;
|
||||
uint32_t txTime = (databits + stopbits + 2) * framesize * 1000.0 / baud;
|
||||
// +1 ms margin for the startbit +1 ms for pending processing time
|
||||
uint32_t txTime = (databits + stopbits + 1) * framesize * 1000.0 / baud;
|
||||
// +1 for the startbit
|
||||
|
||||
return round(txTime);
|
||||
}
|
||||
@ -198,6 +204,7 @@ void clock_init(void) {
|
||||
assert(ClockTask); // has clock task started?
|
||||
} // clock_init
|
||||
|
||||
|
||||
void clock_loop(void *taskparameter) { // ClockTask
|
||||
|
||||
// caveat: don't use now() in this task, it will cause a race condition
|
||||
@ -205,29 +212,49 @@ void clock_loop(void *taskparameter) { // ClockTask
|
||||
|
||||
#define nextmin(t) (t + DCF77_FRAME_SIZE + 1) // next minute
|
||||
|
||||
static bool led1_state = false;
|
||||
uint32_t printtime;
|
||||
time_t t = *((time_t *)taskparameter); // UTC time seconds
|
||||
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
|
||||
TickType_t startTime;
|
||||
|
||||
// preload first DCF frame before start
|
||||
#ifdef HAS_DCF77
|
||||
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
|
||||
DCFpulse = DCF77_Frame(nextmin(t));
|
||||
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
|
||||
DCFpulse = DCF77_Frame(nextmin(t)); // load first DCF frame before start
|
||||
#elif defined HAS_IF482
|
||||
static TickType_t txDelay = pdMS_TO_TICKS(1000 - IF482_SYNC_FIXUP) -
|
||||
tx_Ticks(IF482_FRAME_SIZE, HAS_IF482);
|
||||
#endif
|
||||
|
||||
// output the next second's pulse after timepulse arrived
|
||||
for (;;) {
|
||||
// ensure the notification state is not already pending
|
||||
xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
|
||||
portMAX_DELAY); // wait for timepulse
|
||||
|
||||
startTime = xTaskGetTickCount();
|
||||
|
||||
t = time_t(printtime); // UTC time seconds
|
||||
|
||||
// no confident time -> suppress clock output
|
||||
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)))
|
||||
// no confident or no recent time -> suppress clock output
|
||||
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
|
||||
(t == last_printtime))
|
||||
continue;
|
||||
|
||||
last_printtime = t;
|
||||
|
||||
// pps blink on secondary LED if we have one
|
||||
#ifdef HAS_TWO_LED
|
||||
if (led1_state)
|
||||
switch_LED1(LED_OFF);
|
||||
else
|
||||
switch_LED1(LED_ON);
|
||||
led1_state = !led1_state;
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482
|
||||
|
||||
IF482_Pulse(t);
|
||||
vTaskDelayUntil(&startTime, txDelay); // wait until moment to fire
|
||||
IF482.print(IF482_Frame(t + 1)); // note: if482 telegram for *next* second
|
||||
|
||||
#elif defined HAS_DCF77
|
||||
|
||||
|
167
src/timesync.cpp
167
src/timesync.cpp
@ -27,7 +27,6 @@ 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;
|
||||
typedef std::chrono::duration<double> myClock_secTick;
|
||||
|
||||
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||
@ -37,7 +36,7 @@ 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");
|
||||
// ESP_LOGI(TAG, "Timeserver sync request already pending");
|
||||
return;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
||||
@ -63,11 +62,11 @@ void send_timesync_req() {
|
||||
// task for sending time sync requests
|
||||
void process_timesync_req(void *taskparameter) {
|
||||
|
||||
uint32_t seq_no = 0, time_to_set_us, time_to_set_ms;
|
||||
uint16_t time_to_set_fraction_msec;
|
||||
uint8_t k = 0, i = 0;
|
||||
uint16_t time_to_set_fraction_msec;
|
||||
uint32_t seq_no = 0;
|
||||
time_t time_to_set;
|
||||
auto time_offset = myClock_msecTick::zero();
|
||||
auto time_offset_ms = myClock_msecTick::zero();
|
||||
|
||||
// wait until we are joined
|
||||
while (!LMIC.devaddr) {
|
||||
@ -78,7 +77,7 @@ void process_timesync_req(void *taskparameter) {
|
||||
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;
|
||||
time_sync_seqNo = (time_sync_seqNo < 255) ? time_sync_seqNo + 1 : 0;
|
||||
|
||||
// send sync request to server
|
||||
payload.reset();
|
||||
@ -90,80 +89,84 @@ void process_timesync_req(void *taskparameter) {
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
||||
(seq_no != time_sync_seqNo)) {
|
||||
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver handshake failed", millis() / 1000.0);
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
||||
millis() / 1000.0);
|
||||
goto finish;
|
||||
} // no valid sequence received before timeout
|
||||
|
||||
else { // calculate time diff from collected timestamps
|
||||
k = seq_no % TIME_SYNC_SAMPLES;
|
||||
|
||||
auto t_tx = time_point_cast<milliseconds>(
|
||||
time_sync_tx[k]); // timepoint when node TX_completed
|
||||
auto t_rx = time_point_cast<milliseconds>(
|
||||
time_sync_rx[k]); // timepoint when message was seen on gateway
|
||||
|
||||
time_offset += t_rx - t_tx; // cumulate timepoint diffs
|
||||
// cumulate timepoint diffs
|
||||
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
|
||||
time_point_cast<milliseconds>(time_sync_tx[k]);
|
||||
|
||||
if (i < TIME_SYNC_SAMPLES - 1) {
|
||||
// wait until next cycle
|
||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||
} else {
|
||||
// send flush to open a receive window for last time_sync_ans
|
||||
payload.reset();
|
||||
payload.addByte(0x99);
|
||||
SendPayload(RCMDPORT, prio_high);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
} // for
|
||||
|
||||
// calculate time offset from collected diffs
|
||||
time_offset /= TIME_SYNC_SAMPLES;
|
||||
ESP_LOGD(TAG, "[%0.3f] avg time diff: %0.3f sec", millis() / 1000.0,
|
||||
myClock_secTick(time_offset).count());
|
||||
// begin of time critical section: lock I2C bus to ensure accurate timing
|
||||
I2C_MUTEX_LOCK();
|
||||
|
||||
// calculate absolute time offset with millisecond precision using time base
|
||||
// of LMIC os, since we use LMIC's ostime_t txEnd as tx timestamp
|
||||
time_offset += milliseconds(osticks2ms(os_getTime()));
|
||||
// apply calibration factor for processing time
|
||||
time_offset += milliseconds(TIME_SYNC_FIXUP);
|
||||
// convert to seconds
|
||||
time_to_set = static_cast<time_t>(myClock_secTick(time_offset).count());
|
||||
// average time offset from collected diffs
|
||||
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.
|
||||
// Finally apply calibration const for processing time.
|
||||
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 fraction milliseconds
|
||||
time_to_set_fraction_msec = static_cast<uint16_t>(time_offset.count() % 1000);
|
||||
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)) {
|
||||
if (abs(time_offset.count()) >=
|
||||
TIME_SYNC_TRIGGER) { // milliseconds threshold
|
||||
|
||||
// wait until top of second
|
||||
uint16_t const wait_ms = 1000 - time_to_set_fraction_msec;
|
||||
ESP_LOGD(TAG, "[%0.3f] waiting %d ms", millis() / 1000.0, wait_ms);
|
||||
vTaskDelay(pdMS_TO_TICKS(wait_ms));
|
||||
// wait until top of second with 4ms precision
|
||||
vTaskDelay(pdMS_TO_TICKS(1000 - time_to_set_fraction_msec));
|
||||
|
||||
// sync timer pps to top of second
|
||||
if (ppsIRQ) {
|
||||
timerRestart(ppsIRQ); // reset pps timer
|
||||
CLOCKIRQ(); // fire clock pps interrupt
|
||||
}
|
||||
|
||||
setTime(++time_to_set); // +1 sec after waiting for top of seceond
|
||||
#ifdef HAS_RTC
|
||||
set_rtctime(time_to_set); // calibrate RTC if we have one
|
||||
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
|
||||
|
||||
timeSource = _lora;
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60,
|
||||
timeSync); // set to regular repeat
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time adjusted by %.3f sec",
|
||||
millis() / 1000.0, myClock_secTick(time_offset).count());
|
||||
} else
|
||||
ESP_LOGI(TAG, "Timesync finished, time not adjusted, is up to date");
|
||||
#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); // set to regular repeat
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was adjusted",
|
||||
millis() / 1000.0);
|
||||
} else
|
||||
ESP_LOGW(TAG, "Invalid time received from timeserver");
|
||||
ESP_LOGW(TAG, "[%0.3f] Timesync failed, outdated time calculated",
|
||||
millis() / 1000.0);
|
||||
|
||||
finish:
|
||||
|
||||
@ -188,36 +191,54 @@ void store_time_sync_req(uint32_t t_txEnd_ms) {
|
||||
int 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))
|
||||
if (!lora_time_sync_pending)
|
||||
return 0; // failure
|
||||
|
||||
uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES;
|
||||
uint16_t timestamp_msec; // convert 1/250th sec fractions to ms
|
||||
uint32_t timestamp_sec;
|
||||
// if no time is available or spurious buffer then exit
|
||||
if (buf_len != TIME_SYNC_FRAME_LENGTH) {
|
||||
if (buf[0] == 0xff)
|
||||
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
|
||||
}
|
||||
|
||||
// get the timeserver time.
|
||||
// The first 4 bytes contain the UTC seconds since unix epoch.
|
||||
// Octet order is big endian. Casts are necessary, because buf is an array
|
||||
// of single byte values, and they might overflow when shifted
|
||||
timestamp_sec = ((uint32_t)buf[4]) | (((uint32_t)buf[3]) << 8) |
|
||||
(((uint32_t)buf[2]) << 16) | (((uint32_t)buf[1]) << 24);
|
||||
else { // we received a probably valid time frame
|
||||
|
||||
// The 5th byte contains the fractional seconds in 2^-8 second steps
|
||||
timestamp_msec = 4 * buf[5];
|
||||
uint8_t seq_no = buf[0], k = seq_no % TIME_SYNC_SAMPLES;
|
||||
uint16_t timestamp_msec; // convert 1/250th sec fractions to ms
|
||||
uint32_t timestamp_sec;
|
||||
|
||||
if ((timestamp_sec) || (timestamp_msec)) // field validation: time not 0 ?
|
||||
// fetch timeserver time from 4 bytes containing the UTC seconds since
|
||||
// unix epoch. Octet order is big endian. Casts are necessary, because buf
|
||||
// is an array of single byte values, and they might overflow when shifted
|
||||
timestamp_sec = ((uint32_t)buf[4]) | (((uint32_t)buf[3]) << 8) |
|
||||
(((uint32_t)buf[2]) << 16) | (((uint32_t)buf[1]) << 24);
|
||||
|
||||
// the 5th byte contains the fractional seconds in 2^-8 second steps
|
||||
timestamp_msec = 4 * buf[5];
|
||||
|
||||
// construct the timepoint when message was seen on gateway
|
||||
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
|
||||
else
|
||||
return 0; // failure
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
|
||||
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec);
|
||||
// guess timepoint is recent if newer than code compile date
|
||||
if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
|
||||
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec);
|
||||
|
||||
// inform processing task
|
||||
if (timeSyncReqTask)
|
||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||
// inform processing task
|
||||
if (timeSyncReqTask)
|
||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||
|
||||
return 1; // success
|
||||
return 1; // success
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: outdated time received",
|
||||
millis() / 1000.0);
|
||||
return 0; // failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user