Merge pull request #323 from cyberman54/development

v1.7.4
This commit is contained in:
Verkehrsrot 2019-03-24 20:14:45 +01:00 committed by GitHub
commit 77d1efede9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 474 additions and 258 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
#ifdef USE_OTA
#if (USE_OTA)
/*
Parts of this code:

View File

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

View File

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

View File

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

View File

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

View File

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