diff --git a/README.md b/README.md
index dbc2e27a..5aafe240 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ This can all be done with a single small and cheap ESP32 board for less than $20
*LoRa & SPI*:
- Heltec: LoRa-32
-- TTGO: T3_v1, T3_v2, T3_v2.1, T-Beam
+- TTGO: T1, T2, T3, T-Beam, T-Fox
- Pycom: LoPy, LoPy4, FiPy
- WeMos: LoLin32 + [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora),
LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-Lora)
@@ -49,6 +49,8 @@ Depending on board hardware following features are supported:
- Battery voltage monitoring
- GPS (Generic serial NMEA, or Quectel L76 I2C)
- Environmental sensor (Bosch BME680 I2C)
+- Real Time Clock (Maxim DS3231 I2C)
+- IF482 time telegram generator (serial port)
Target platform must be selected in [platformio.ini](https://github.com/cyberman54/ESP32-Paxcounter/blob/master/platformio.ini).
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.
@@ -78,6 +80,8 @@ If your device has a fixed DEVEUI enter this in your local loraconf.h file. Duri
If your device has silicon **Unique ID** which is stored in serial EEPROM Microchip 24AA02E64 you don't need to change anything. The Unique ID will be read during startup and DEVEUI will be generated from it, overriding settings in loraconf.h.
+If your device has a **real time clock** date/time of rtc will be updated bei either LoRaWAN network or GPS time (if present).
+
# Building
Use PlatformIO with your preferred IDE for development and building this code. Make sure you have latest PlatformIO version.
diff --git a/include/cyclic.h b/include/cyclic.h
index 6e5df0cd..8e578009 100644
--- a/include/cyclic.h
+++ b/include/cyclic.h
@@ -11,6 +11,11 @@
#include "bme680mems.h"
#endif
+// Needed for RTC time sync if RTC present on board
+#ifdef HAS_RTC
+#include "rtctime.h"
+#endif
+
void doHousekeeping(void);
void do_timesync(void);
uint64_t uptime(void);
diff --git a/include/globals.h b/include/globals.h
index a7450227..890e93cc 100644
--- a/include/globals.h
+++ b/include/globals.h
@@ -9,7 +9,6 @@
#include
#include
#include "mallocator.h"
-//#include "inc/bsec_datatypes.h"
#include "../lib/Bosch-BSEC/src/inc/bsec_datatypes.h"
// sniffing types
@@ -36,6 +35,10 @@
#define BLE_MODE (0x40)
#define SCREEN_MODE (0x80)
+// I2C bus access control
+#define I2C_MUTEX_LOCK() xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == pdTRUE
+#define I2C_MUTEX_UNLOCK() xSemaphoreGive(I2Caccess)
+
// Struct holding devices's runtime configuration
typedef struct {
uint8_t lorasf; // 7-12, lora spreadfactor
@@ -137,4 +140,8 @@ extern TaskHandle_t irqHandlerTask, wifiSwitchTask;
#include "bme680mems.h"
#endif
+#ifdef HAS_IF482
+#include "if482.h"
+#endif
+
#endif
\ No newline at end of file
diff --git a/include/gpsread.h b/include/gpsread.h
index 7f432c47..f98e4d62 100644
--- a/include/gpsread.h
+++ b/include/gpsread.h
@@ -2,7 +2,7 @@
#define _GPSREAD_H
#include // library for parsing NMEA data
-#include
+#include
#ifdef GPS_I2C // Needed for reading from I2C Bus
#include
diff --git a/include/if482.h b/include/if482.h
new file mode 100644
index 00000000..7ceca1c5
--- /dev/null
+++ b/include/if482.h
@@ -0,0 +1,13 @@
+#ifndef _IF482_H
+#define _IF482_H
+
+#include "globals.h"
+#include "irqhandler.h"
+
+extern TaskHandle_t IF482Task;
+
+int if482_init(void);
+void if482_loop(void *pvParameters);
+void IRAM_ATTR IF482IRQ(void);
+
+#endif
\ No newline at end of file
diff --git a/include/irqhandler.h b/include/irqhandler.h
index d068df32..45d58fee 100644
--- a/include/irqhandler.h
+++ b/include/irqhandler.h
@@ -25,4 +25,4 @@ void IRAM_ATTR DisplayIRQ();
void IRAM_ATTR ButtonIRQ();
#endif
-#endif
+#endif
\ No newline at end of file
diff --git a/include/lorawan.h b/include/lorawan.h
index 7c8e0d27..2bd1b55b 100644
--- a/include/lorawan.h
+++ b/include/lorawan.h
@@ -17,6 +17,11 @@
#include
#endif
+// Needed for RTC time sync if RTC present on board
+#ifdef HAS_RTC
+#include "rtctime.h"
+#endif
+
extern QueueHandle_t LoraSendQueue;
void onEvent(ev_t ev);
diff --git a/include/rtctime.h b/include/rtctime.h
new file mode 100644
index 00000000..e308ec20
--- /dev/null
+++ b/include/rtctime.h
@@ -0,0 +1,32 @@
+#ifndef _RTCTIME_H
+#define _RTCTIME_H
+
+#include "globals.h"
+#include
+#include
+#include // must be included here so that Arduino library object file references work
+#include
+
+#ifdef HAS_GPS
+#include "gpsread.h"
+#endif
+
+typedef enum {
+ useless = 0, // waiting for good enough signal
+ dirty = 1, // time data available but inconfident
+ reserve = 2, // clock was once synced but now may deviate
+ synced_LORA = 3, // clock driven by LORAWAN network
+ synced_GPS = 4 // best possible quality, clock is driven by GPS
+} clock_state_t;
+
+extern RtcDS3231 Rtc; // make RTC instance globally available
+extern Timezone myTZ; // make Timezone myTZ globally available
+
+int rtc_init(void);
+int set_rtctime(uint32_t UTCTime);
+int set_rtctime(RtcDateTime now);
+void sync_rtctime(void);
+time_t get_rtctime(void);
+float get_rtctemp(void);
+
+#endif // _RTCTIME_H
\ No newline at end of file
diff --git a/platformio.ini b/platformio.ini
index c7522f70..766a9396 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -30,7 +30,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
[common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
-release_version = 1.7.11
+release_version = 1.7.14
; 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
@@ -45,11 +45,13 @@ monitor_speed = 115200
lib_deps_lora =
MCCI LoRaWAN LMIC library@^2.3.1
lib_deps_display =
- U8g2@>=2.25.0
+ U8g2@>=2.25.5
lib_deps_rgbled =
SmartLeds@>=1.1.3
lib_deps_gps =
TinyGPSPlus@>=1.0.2
+lib_deps_rtc =
+ RTC@^2.3.0
lib_deps_basic =
ArduinoJson@^5.13.1
Time@>=1.5
@@ -59,6 +61,7 @@ lib_deps_all =
${common.lib_deps_display}
${common.lib_deps_rgbled}
${common.lib_deps_gps}
+ ${common.lib_deps_rtc}
build_flags_basic =
-include "src/hal/${PIOENV}.h"
-include "src/paxcounter.conf"
@@ -212,7 +215,8 @@ lib_deps =
${common.lib_deps_basic}
${common.lib_deps_lora}
${common.lib_deps_display}
-build_flags =
+ ${common.lib_deps_rtc}
+build_flags =
${common.build_flags_basic}
upload_protocol = ${common.upload_protocol}
extra_scripts = ${common.extra_scripts}
diff --git a/src/blecsan.cpp b/src/blecsan.cpp
index fa8a09c4..1e3d0d2d 100644
--- a/src/blecsan.cpp
+++ b/src/blecsan.cpp
@@ -1,5 +1,3 @@
-#ifdef BLECOUNTER
-
/* code snippets taken from
https://github.com/nkolban/esp32-snippets/tree/master/BLE/scanner
*/
@@ -235,8 +233,6 @@ esp_err_t register_ble_callback(void) {
} // register_ble_callback
-#endif // BLECOUNTER
-
void start_BLEscan(void) {
#ifdef BLECOUNTER
ESP_LOGI(TAG, "Initializing bluetooth scanner ...");
diff --git a/src/bme680mems.cpp b/src/bme680mems.cpp
index bb420db4..924bc656 100644
--- a/src/bme680mems.cpp
+++ b/src/bme680mems.cpp
@@ -32,8 +32,7 @@ int bme_init(void) {
// return = 0 -> error / return = 1 -> success
// block i2c bus access
- if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) ==
- pdTRUE) {
+ if (I2C_MUTEX_LOCK()) {
Wire.begin(HAS_BME);
iaqSensor.begin(BME_ADDR, Wire);
@@ -62,17 +61,16 @@ int bme_init(void) {
ESP_LOGE(TAG, "BSEC subscription error");
goto error;
}
-
} else {
ESP_LOGE(TAG, "I2c bus busy - BME680 initialization error");
goto error;
}
- xSemaphoreGive(I2Caccess); // release i2c bus access
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
return 1;
error:
- xSemaphoreGive(I2Caccess); // release i2c bus access
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
return 0;
} // bme_init()
@@ -106,24 +104,24 @@ void bme_loop(void *pvParameters) {
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
#ifdef HAS_BME
- // block i2c bus access
- while (xSemaphoreTake(I2Caccess, portMAX_DELAY) == pdTRUE) {
-
- if (iaqSensor.run()) { // If new data is available
- bme_status.raw_temperature = iaqSensor.rawTemperature;
- bme_status.raw_humidity = iaqSensor.rawHumidity;
- bme_status.temperature = iaqSensor.temperature;
- bme_status.humidity = iaqSensor.humidity;
- bme_status.pressure =
- (iaqSensor.pressure / 100.0); // conversion Pa -> hPa
- bme_status.iaq = iaqSensor.iaqEstimate;
- bme_status.iaq_accuracy = iaqSensor.iaqAccuracy;
- bme_status.gas = iaqSensor.gasResistance;
- updateState();
+ while (1) {
+ // block i2c bus access
+ if (I2C_MUTEX_LOCK()) {
+ if (iaqSensor.run()) { // If new data is available
+ bme_status.raw_temperature = iaqSensor.rawTemperature;
+ bme_status.raw_humidity = iaqSensor.rawHumidity;
+ bme_status.temperature = iaqSensor.temperature;
+ bme_status.humidity = iaqSensor.humidity;
+ bme_status.pressure =
+ (iaqSensor.pressure / 100.0); // conversion Pa -> hPa
+ bme_status.iaq = iaqSensor.iaqEstimate;
+ bme_status.iaq_accuracy = iaqSensor.iaqAccuracy;
+ bme_status.gas = iaqSensor.gasResistance;
+ updateState();
+ }
+ I2C_MUTEX_UNLOCK();
}
- xSemaphoreGive(I2Caccess); // release i2c bus access
-
- } // while
+ }
#endif
ESP_LOGE(TAG, "BME task ended");
vTaskDelete(BmeTask); // should never be reached
diff --git a/src/cyclic.cpp b/src/cyclic.cpp
index 13518cf5..dc9fe12f 100644
--- a/src/cyclic.cpp
+++ b/src/cyclic.cpp
@@ -123,25 +123,30 @@ void reset_counters() {
void do_timesync() {
#ifdef TIME_SYNC_INTERVAL
-// sync time & date by GPS if we have valid gps time
+// set system time to time source GPS, if we have valid gps time
#ifdef HAS_GPS
if (gps.time.isValid()) {
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
gps.date.day(), gps.date.month(), gps.date.year());
- ESP_LOGI(TAG, "Time synced by GPS to %02d:%02d:%02d", hour(), minute(),
- second());
+// set RTC time to time source GPS, if RTC is present
+#ifdef HAS_RTC
+ if (!set_rtctime(RtcDateTime(now())))
+ ESP_LOGE(TAG, "RTC set time failure");
+#endif
+ time_t tt = myTZ.toLocal(now());
+ ESP_LOGI(TAG, "GPS has set system time to %02d/%02d/%d %02d:%02d:%02d",
+ month(tt), day(tt), year(tt), hour(tt), minute(tt), second(tt));
return;
} else {
ESP_LOGI(TAG, "No valid GPS time");
}
-#endif // HAS_GPS
-// sync time by LoRa Network if network supports DevTimeReq
-#ifdef LMIC_ENABLE_DeviceTimeReq
- // Schedule a network time request at the next possible time
+ // set system time to time source LoRa Network, if network supports DevTimeReq
+#elif defined LMIC_ENABLE_DeviceTimeReq
+ // Schedule a network time sync request at the next possible time
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
ESP_LOGI(TAG, "Network time request scheduled");
-#endif
+#endif // HAS_GPS
#endif // TIME_SYNC_INTERVAL
} // do_timesync()
diff --git a/src/display.cpp b/src/display.cpp
index 861328fd..7f9adbea 100644
--- a/src/display.cpp
+++ b/src/display.cpp
@@ -1,5 +1,27 @@
#ifdef HAS_DISPLAY
+/*
+
+Display-Mask (128 x 64 pixel):
+
+ | 111111
+ |0123456789012345
+------------------
+0|PAX:aabbccddee
+1|PAX:aabbccddee
+2|B:a.bcV Sats:ab
+3|BLTH:abcde SF:ab
+4|WIFI:abcde ch:ab
+5|RLIM:abcd abcdKB
+6|xxxxxxxxxxxxxxxx
+6|20:27:00* 27.Feb
+7|yyyyyyyyyyyyyyab
+
+line 6: x = Text for LORA status OR time/date
+line 7: y = Text for LMIC status; ab = payload queue
+
+*/
+
// Basic Config
#include "globals.h"
#include // needed for reading ESP32 chip attributes
@@ -19,6 +41,10 @@ const char lora_datarate[] = {"1211100908078CNA1211109C8C7C"};
const char lora_datarate[] = {"121110090807FSNA"};
#endif
+// helper arry for converting month values to text
+char *printmonth[] = {"xxx", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
uint8_t volatile DisplayState = 0;
// helper function, prints a hex key on display
@@ -99,8 +125,7 @@ void init_display(const char *Productname, const char *Version) {
void refreshtheDisplay() {
// block i2c bus access
- if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) ==
- pdTRUE) {
+ if (I2C_MUTEX_LOCK()) {
// set display on/off according to current device configuration
if (DisplayState != cfg.screenon) {
@@ -126,26 +151,29 @@ void refreshtheDisplay() {
// update Battery status (line 2)
#ifdef HAS_BATTERY_PROBE
u8x8.setCursor(0, 2);
- u8x8.printf("B:%.1fV", batt_voltage / 1000.0);
+ u8x8.printf("B:%.2fV", batt_voltage / 1000.0);
#endif
// update GPS status (line 2)
#ifdef HAS_GPS
- u8x8.setCursor(9, 2);
- if (!gps.location.isValid()) // if no fix then display Sats value inverse
- {
- u8x8.setInverseFont(1);
- u8x8.printf("Sats:%.2d", gps.satellites.value());
- u8x8.setInverseFont(0);
- } else
- u8x8.printf("Sats:%.2d", gps.satellites.value());
+ // have we ever got valid gps data?
+ if (gps.passedChecksum() > 0) {
+ u8x8.setCursor(9, 2);
+ if (!gps.location.isValid()) // if no fix then display Sats value inverse
+ {
+ u8x8.setInverseFont(1);
+ u8x8.printf("Sats:%.2d", gps.satellites.value());
+ u8x8.setInverseFont(0);
+ } else
+ u8x8.printf("Sats:%.2d", gps.satellites.value());
+ }
#endif
- // update bluetooth counter + LoRa SF (line 3)
+ // update bluetooth counter + LoRa SF (line 3)
#ifdef BLECOUNTER
u8x8.setCursor(0, 3);
if (cfg.blescan)
- u8x8.printf("BLTH:%-4d", macs_ble);
+ u8x8.printf("BLTH:%-5d", macs_ble);
else
u8x8.printf("%s", "BLTH:off");
#endif
@@ -163,7 +191,7 @@ void refreshtheDisplay() {
// update wifi counter + channel display (line 4)
u8x8.setCursor(0, 4);
- u8x8.printf("WIFI:%-4d", macs_wifi);
+ u8x8.printf("WIFI:%-5d", macs_wifi);
u8x8.setCursor(11, 4);
u8x8.printf("ch:%02d", channel);
@@ -174,9 +202,18 @@ void refreshtheDisplay() {
u8x8.printf("%4dKB", getFreeRAM() / 1024);
#ifdef HAS_LORA
- // update LoRa status display (line 6)
+
u8x8.setCursor(0, 6);
+#ifndef HAS_RTC
+ // update LoRa status display (line 6)
u8x8.printf("%-16s", display_line6);
+#else
+ // update time/date display (line 6)
+ time_t t = myTZ.toLocal(now());
+ u8x8.printf("%02d:%02d:%02d%c %2d.%3s", hour(t), minute(t), second(t),
+ timeStatus() == timeSet ? '*' : '?', day(t),
+ printmonth[month(t)]);
+#endif
// update LMiC event display (line 7)
u8x8.setCursor(0, 7);
@@ -193,7 +230,7 @@ void refreshtheDisplay() {
#endif // HAS_LORA
- xSemaphoreGive(I2Caccess); // release i2c bus access
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
}
} // refreshDisplay()
diff --git a/src/gpsread.cpp b/src/gpsread.cpp
index 48f5a451..55d8ba2d 100644
--- a/src/gpsread.cpp
+++ b/src/gpsread.cpp
@@ -10,7 +10,7 @@ gpsStatus_t gps_status;
TaskHandle_t GpsTask;
#ifdef GPS_SERIAL
-HardwareSerial GPS_Serial(1);
+HardwareSerial GPS_Serial(1); // use UART #1
#endif
// initialize and configure GPS
diff --git a/src/hal/generic.h b/src/hal/generic.h
index c4e6deb9..1c9d3b39 100644
--- a/src/hal/generic.h
+++ b/src/hal/generic.h
@@ -6,6 +6,7 @@
#include
// Hardware related definitions for generic ESP32 boards
+// generic.h is kitchensink with all available options
#define HAS_LORA 1 // comment out if device shall not send data via LoRa or has no LoRa
#define HAS_SPI 1 // comment out if device shall not send data via SPI
@@ -30,7 +31,7 @@
#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 // uses GPIO7
+#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 HAS_LED (21) // on board LED
@@ -47,6 +48,9 @@
#define MY_OLED_SCL (15)
#define MY_OLED_RST (16)
+// Pins for on board DS3231 RTC chip
+#define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL
+
// Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5)
#define LORA_CS (18)
diff --git a/src/hal/ttgobeam.h b/src/hal/ttgobeam.h
index 4b823e04..8dd12bd4 100644
--- a/src/hal/ttgobeam.h
+++ b/src/hal/ttgobeam.h
@@ -24,13 +24,13 @@
// enable only if device has these sensors, otherwise comment these lines
// BME680 sensor on I2C bus
-//#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL
-//#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND
+//#define HAS_BME SDA, SCL
+//#define BME_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !!
// display (if connected)
//#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
-//#define MY_OLED_SDA (21)
-//#define MY_OLED_SCL (22)
+//#define MY_OLED_SDA SDA
+//#define MY_OLED_SCL SCL
//#define MY_OLED_RST U8X8_PIN_NONE
//#define DISPLAY_FLIP 1 // use if display is rotated
diff --git a/src/hal/ttgofox.h b/src/hal/ttgofox.h
index 8f0a6d2d..414cedd1 100644
--- a/src/hal/ttgofox.h
+++ b/src/hal/ttgofox.h
@@ -9,9 +9,9 @@
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
-#define HAS_LED NOT_A_PIN // green on board LED
-//#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW
-#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // uses GPIO7
+#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 HAS_BUTTON GPIO_NUM_36 // on board button (next to reset)
@@ -20,6 +20,13 @@
#define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE
+// Pins for on board DS3231 RTC chip
+#define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL
+#define RTC_INT GPIO_NUM_34 // interrupt input from rtc
+
+// Settings for IF482 interface
+//#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters
+
// Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5)
#define LORA_CS (18)
diff --git a/src/hal/ttgov21new.h b/src/hal/ttgov21new.h
index 6e21a65a..f2cf0571 100644
--- a/src/hal/ttgov21new.h
+++ b/src/hal/ttgov21new.h
@@ -15,7 +15,7 @@
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_LED (25) // green on board LED
-#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // uses GPIO7
+#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
#define BATT_FACTOR 2 // voltage divider 100k/100k on board
// Pins for I2C interface of OLED Display
diff --git a/src/hal/ttgov21old.h b/src/hal/ttgov21old.h
index 21cdd628..d3dd5572 100644
--- a/src/hal/ttgov21old.h
+++ b/src/hal/ttgov21old.h
@@ -18,7 +18,7 @@
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
//#define DISPLAY_FLIP 1 // rotated display
-//#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // uses GPIO7
+//#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // battery probe GPIO pin -> ADC1_CHANNEL_7
//#define BATT_FACTOR 2 // voltage divider 100k/100k on board
// Pins for I2C interface of OLED Display
diff --git a/src/if482.cpp b/src/if482.cpp
new file mode 100644
index 00000000..48d98a47
--- /dev/null
+++ b/src/if482.cpp
@@ -0,0 +1,160 @@
+#if defined HAS_IF482 && defined HAS_RTC
+
+/*
+
+IF482 Generator to control clocks with IF482 telegram input (e.g. BÜRK BU190)
+
+Example IF482 telegram: "OAL160806F170400"
+
+IF482 Specification:
+http://www.mobatime.com/fileadmin/user_upload/downloads/TE-112023.pdf
+
+The IF 482 telegram is a time telegram, which sends the time and date
+information as ASCII characters through the serial interface RS 232 or RS 422.
+
+Communication parameters:
+
+Baud rate: 9600 Bit/s
+Data bits 7
+Parity: even
+Stop bit: 1
+Jitter: < 50ms
+
+Interface : RS232 or RS422
+
+Synchronization: Telegram ends at the beginning of the second
+specified in the telegram
+
+Cycle: 1 second
+
+Format of ASCII telegram string:
+
+Byte Meaning ASCII Hex
+ 1 Start of telegram O 4F
+ 2 Monitoring* A 41
+ 3 Time-Season** W/S/U/L 57 or 53
+ 4 Year tens 0 .. 9 30 .. 39
+ 5 Year unit 0 .. 9 30 .. 39
+ 6 Month tens 0 or 1 30 or 31
+ 7 Month unit 0 .. 9 30 .. 39
+ 8 Day tens 0 .. 3 30 .. 33
+ 9 Day unit 0 .. 9 30 .. 39
+10 Day of week*** 1 .. 7 31 .. 37
+11 Hours tens 0 .. 2 30 .. 32
+12 Hours unit 0 .. 9 30 .. 39
+13 Minutes tens 0 .. 5 30 .. 35
+14 Minutes unit 0 .. 9 30 .. 39
+15 Seconds tens 0 .. 5 30 .. 35
+16 Seconds unit 0 .. 9 30 .. 39
+17 End of telegram CR 0D
+
+*) Monitoring:
+With a correctly received time in the sender unit, the ASCII character 'A' is
+issued. If 'M' is issued, this indicates that the sender was unable to receive
+any time signal for over 12 hours (time is accepted with ‘A’ and ‘M’).
+
+**) Season:
+W: Standard time,
+S: Season time,
+U: UTC time (not supported by all systems),
+L: Local Time
+
+***) Day of week:
+not evaluated by model BU-190
+
+*/
+
+#include "if482.h"
+
+// Local logging tag
+static const char TAG[] = "main";
+
+TaskHandle_t IF482Task;
+
+HardwareSerial IF482(2); // use UART #2 (note: #1 may be in use for serial GPS)
+
+// initialize and configure GPS
+int if482_init(void) {
+
+ // open serial interface
+ IF482.begin(HAS_IF482);
+
+ // use rtc 1Hz clock for triggering IF482 telegram send
+ Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
+ Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
+ pinMode(RTC_INT, INPUT_PULLUP);
+
+ ESP_LOGI(TAG, "IF482 generator initialized");
+
+ return 1;
+
+} // if482_init
+
+String if482Telegram(time_t t) {
+
+ char mon;
+ char buf[14] = "000000F000000";
+ char out[17];
+
+ switch (timeStatus()) { // indicates if time has been set and recently synced
+ case timeSet: // time is set and is synced
+ mon = 'A';
+ break;
+ case timeNeedsSync: // time had been set but sync attempt did not succeed
+ mon = 'M';
+ break;
+ default: // time not set, no valid time
+ mon = '?';
+ break;
+ } // switch
+
+ if (!timeNotSet) // do we have valid time?
+ snprintf(buf, sizeof buf, "%02u%02u%02u%1u%02u%02u%02u", year(t) - 2000,
+ month(t), day(t), weekday(t), hour(t), minute(t), second(t));
+
+ snprintf(out, sizeof out, "O%cL%s\r", mon, buf);
+ return out;
+}
+
+void if482_loop(void *pvParameters) {
+
+ configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
+
+ TickType_t wakeTime;
+ time_t t, tt;
+ const TickType_t shotTime = pdMS_TO_TICKS(IF482_OFFSET);
+
+ // wait until begin of a new second
+ t = tt = now();
+ do {
+ tt = now();
+ } while (t == tt);
+
+ const TickType_t startOffset = xTaskGetTickCount();
+
+ // task remains in blocked state until it is notified by isr
+ for (;;) {
+ xTaskNotifyWait(
+ 0x00, // don't clear any bits on entry
+ ULONG_MAX, // clear all bits on exit
+ &wakeTime, // receives moment of call from isr
+ portMAX_DELAY); // wait forever (missing error handling here...)
+
+ t = myTZ.toLocal(now());
+ wakeTime -= startOffset;
+
+ // now we're synced to start of second t and wait
+ // until it's time to start transmit telegram for t+1
+ vTaskDelayUntil(&wakeTime, shotTime);
+ IF482.print(if482Telegram(t+1));
+ }
+ vTaskDelete(IF482Task); // shoud never be reached
+} // if482_loop()
+
+// interrupt service routine triggered by RTC 1Hz precise clock
+void IRAM_ATTR IF482IRQ() {
+ xTaskNotifyFromISR(IF482Task, xTaskGetTickCountFromISR(), eSetBits, NULL);
+ portYIELD_FROM_ISR();
+}
+
+#endif // HAS_IF482
\ No newline at end of file
diff --git a/src/irqhandler.cpp b/src/irqhandler.cpp
index a9192a2b..f79e035e 100644
--- a/src/irqhandler.cpp
+++ b/src/irqhandler.cpp
@@ -61,7 +61,7 @@ void IRAM_ATTR SendCycleIRQ() {
#ifdef HAS_DISPLAY
void IRAM_ATTR DisplayIRQ() {
- xTaskNotifyFromISR(irqHandlerTask, DISPLAY_IRQ, eSetBits, NULL);
+ xTaskNotifyFromISR(irqHandlerTask, DISPLAY_IRQ, eSetBits, NULL);
portYIELD_FROM_ISR();
}
#endif
diff --git a/src/lorawan.cpp b/src/lorawan.cpp
index 05cab168..dd200c89 100644
--- a/src/lorawan.cpp
+++ b/src/lorawan.cpp
@@ -455,6 +455,12 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
// Update system time with time read from the network
setTime(*pUserUTCTime);
- ESP_LOGI(TAG, "Time synced by LoRa network to %02d:%02d:%02d", hour(),
- minute(), second());
+#ifdef HAS_RTC
+ if (!set_rtctime(*pUserUTCTime))
+ ESP_LOGE(TAG, "RTC set time failure");
+#endif
+ time_t t = myTZ.toLocal(now());
+ ESP_LOGI(TAG,
+ "LORA Network has set system time to %02d/%02d/%d %02d:%02d:%02d",
+ month(t), day(t), year(t), hour(t), minute(t), second(t));
}
\ No newline at end of file
diff --git a/src/main.cpp b/src/main.cpp
index 609243f8..57350e70 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -29,6 +29,7 @@ Task Core Prio Purpose
====================================================================================
wifiloop 0 4 rotates wifi channels
ledloop 0 3 blinks LEDs
+if482loop 1 3 serial feed of IF482 time telegrams
spiloop 0 2 reads/writes data on spi interface
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
@@ -44,11 +45,15 @@ Tasks using i2c bus all must have same priority, because using mutex semaphore
(irqhandler, bmeloop)
ESP32 hardware timers
-==========================
- 0 Trigger display refresh
- 1 Trigger Wifi channel switch
- 2 Trigger send payload cycle
- 3 Trigger housekeeping cycle
+================================
+ 0 triggers display refresh
+ 1 triggers Wifi channel switch
+ 2 triggers send payload cycle
+ 3 triggers housekeeping cycle
+
+ RTC hardware timer (if present)
+================================
+ triggers IF482 clock generator
*/
@@ -147,6 +152,13 @@ void setup() {
strcat_P(features, " PSRAM");
#endif
+// set low power mode to off
+#ifdef HAS_LOWPOWER_SWITCH
+ pinMode(HAS_LED, OUTPUT);
+ digitalWrite(HAS_LOWPOWER_SWITCH, HIGH);
+ strcat_P(features, " LPWR");
+#endif
+
// initialize leds
#if (HAS_LED != NOT_A_PIN)
pinMode(HAS_LED, OUTPUT);
@@ -161,7 +173,7 @@ void setup() {
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
// start led loop
- ESP_LOGI(TAG, "Starting LEDloop...");
+ ESP_LOGI(TAG, "Starting LED Controller...");
xTaskCreatePinnedToCore(ledLoop, // task function
"ledloop", // name of task
1024, // stack size of task
@@ -171,7 +183,26 @@ void setup() {
0); // CPU core
#endif
- // initialize wifi antenna
+// initialize RTC
+#ifdef HAS_RTC
+ strcat_P(features, " RTC");
+ assert(rtc_init());
+ sync_rtctime();
+#ifdef HAS_IF482
+ strcat_P(features, " IF482");
+ assert(if482_init());
+ ESP_LOGI(TAG, "Starting IF482 Generator...");
+ xTaskCreatePinnedToCore(if482_loop, // task function
+ "if482loop", // name of task
+ 2048, // stack size of task
+ (void *)1, // parameter of the task
+ 3, // priority of the task
+ &IF482Task, // task handle
+ 0); // CPU core
+#endif // HAS_IF482
+#endif // HAS_RTC
+
+// initialize wifi antenna
#ifdef HAS_ANTENNA_SWITCH
strcat_P(features, " ANT");
antenna_init();
@@ -233,7 +264,7 @@ void setup() {
#ifdef HAS_GPS
strcat_P(features, " GPS");
if (gps_init()) {
- ESP_LOGI(TAG, "Starting GPSloop...");
+ ESP_LOGI(TAG, "Starting GPS Feed...");
xTaskCreatePinnedToCore(gps_loop, // task function
"gpsloop", // name of task
2048, // stack size of task
@@ -312,7 +343,7 @@ void setup() {
ESP_LOGI(TAG, "Features:%s", features);
#ifdef HAS_LORA
- // output LoRaWAN keys to console
+// output LoRaWAN keys to console
#ifdef VERBOSE
showLoraKeys();
#endif
@@ -327,7 +358,7 @@ void setup() {
get_salt(); // get new 16bit for salting hashes
// start state machine
- ESP_LOGI(TAG, "Starting IRQ Handler...");
+ ESP_LOGI(TAG, "Starting Interrupt Handler...");
xTaskCreatePinnedToCore(irqHandler, // task function
"irqhandler", // name of task
4096, // stack size of task
@@ -346,11 +377,11 @@ void setup() {
&wifiSwitchTask, // task handle
0); // CPU core
- // initialize bme
+// initialize bme
#ifdef HAS_BME
strcat_P(features, " BME");
if (bme_init()) {
- ESP_LOGI(TAG, "Starting BMEloop...");
+ ESP_LOGI(TAG, "Starting Bluetooth sniffer...");
xTaskCreatePinnedToCore(bme_loop, // task function
"bmeloop", // name of task
2048, // stack size of task
@@ -361,7 +392,8 @@ void setup() {
}
#endif
- // start timer triggered interrupts
+ assert(irqHandlerTask != NULL); // has interrupt handler task started?
+ // start timer triggered interrupts
ESP_LOGI(TAG, "Starting Interrupts...");
#ifdef HAS_DISPLAY
timerAlarmEnable(displaytimer);
@@ -370,7 +402,7 @@ void setup() {
timerAlarmEnable(homeCycle);
timerAlarmEnable(channelSwitch);
- // start button interrupt
+// start button interrupt
#ifdef HAS_BUTTON
#ifdef BUTTON_PULLUP
attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING);
@@ -379,6 +411,14 @@ void setup() {
#endif
#endif // HAS_BUTTON
+// start RTC interrupt
+#if defined HAS_IF482 && defined HAS_RTC
+ // setup external interupt for active low RTC INT pin
+ assert(IF482Task != NULL); // has if482loop task started?
+ ESP_LOGI(TAG, "Starting IF482 output...");
+ attachInterrupt(digitalPinToInterrupt(RTC_INT), IF482IRQ, FALLING);
+#endif
+
} // setup()
void loop() {
diff --git a/src/paxcounter.conf b/src/paxcounter.conf
index 007561c2..050fe655 100644
--- a/src/paxcounter.conf
+++ b/src/paxcounter.conf
@@ -81,8 +81,13 @@
#define OTA_MIN_BATT 3600 // minimum battery level for OTA [millivolt]
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
-// setting for syncing time of node
-//#define TIME_SYNC_INTERVAL 60 // sync time each ... minutes [default = 60], comment out means off
+// settings for syncing time of node and external time sources
+#define TIME_SYNC_INTERVAL 60 // sync time each ... minutes with external source [default = 60], comment out means off
+#define TIME_SYNC_INTERVAL_RTC 5 // sync time each ... minutes with RTC [default = 5], comment out means off
+#define IF482_OFFSET 984 // 1sec minus IF482 serial transmit time [ms]: e.g. 9 bits * 17 bytes * 1/9600 bps = 16ms
+// 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
// LMIC settings
-// moved to src/lmic_config.h
+// moved to src/lmic_config.h
\ No newline at end of file
diff --git a/src/rtctime.cpp b/src/rtctime.cpp
new file mode 100644
index 00000000..4262b25e
--- /dev/null
+++ b/src/rtctime.cpp
@@ -0,0 +1,137 @@
+#ifdef HAS_RTC
+
+#include "rtctime.h"
+
+// Local logging tag
+static const char TAG[] = "main";
+
+RtcDS3231 Rtc(Wire); // RTC hardware i2c interface
+
+// set Time Zone, fetch user setting from paxcounter.conf
+TimeChangeRule myDST = DAYLIGHT_TIME;
+TimeChangeRule mySTD = STANDARD_TIME;
+Timezone myTZ(myDST, mySTD);
+
+// initialize RTC
+int rtc_init(void) {
+
+ // return = 0 -> error / return = 1 -> success
+
+ // block i2c bus access
+ if (I2C_MUTEX_LOCK()) {
+
+ Wire.begin(HAS_RTC);
+ Rtc.Begin();
+
+ RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
+
+ if (!Rtc.IsDateTimeValid()) {
+ ESP_LOGW(TAG,
+ "RTC has no valid RTC date/time, setting to compilation date");
+ Rtc.SetDateTime(compiled);
+ }
+
+ if (!Rtc.GetIsRunning()) {
+ ESP_LOGI(TAG, "RTC not running, starting now");
+ Rtc.SetIsRunning(true);
+ }
+
+ RtcDateTime now = Rtc.GetDateTime();
+
+ if (now < compiled) {
+ ESP_LOGI(TAG, "RTC date/time is older than compilation date, updating");
+ Rtc.SetDateTime(compiled);
+ }
+
+ // configure RTC chip
+ Rtc.Enable32kHzPin(false);
+ Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
+
+ } else {
+ ESP_LOGE(TAG, "I2c bus busy - RTC initialization error");
+ goto error;
+ }
+
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
+ ESP_LOGI(TAG, "RTC initialized");
+ return 1;
+
+error:
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
+ return 0;
+
+} // rtc_init()
+
+int set_rtctime(uint32_t UTCTime) {
+ // return = 0 -> error / return = 1 -> success
+ // block i2c bus access
+ if (I2C_MUTEX_LOCK()) {
+ Rtc.SetDateTime(RtcDateTime(UTCTime));
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
+ return 1;
+ }
+ return 0;
+} // set_rtctime()
+
+int set_rtctime(RtcDateTime t) {
+ // return = 0 -> error / return = 1 -> success
+ // block i2c bus access
+ if (I2C_MUTEX_LOCK()) {
+ Rtc.SetDateTime(t);
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
+ return 1;
+ }
+ return 0;
+} // set_rtctime()
+
+time_t get_rtctime(void) {
+ // never call now() in this function, this would cause a recursion!
+ time_t tt = 0;
+ // block i2c bus access
+ if (I2C_MUTEX_LOCK()) {
+ if (!Rtc.IsDateTimeValid()) {
+ ESP_LOGW(TAG, "RTC has no confident time");
+ } else {
+ RtcDateTime t = Rtc.GetDateTime();
+ tt = t.Epoch32Time();
+ }
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
+ return tt;
+ }
+ return tt;
+} // get_rtctime()
+
+void sync_rtctime(void) {
+ if (timeStatus() != timeSet) { // do we need time sync?
+ time_t t = get_rtctime();
+ if (t) { // have we got a valid time from RTC?
+ setTime(t);
+ time_t tt = myTZ.toLocal(t);
+ ESP_LOGI(TAG, "RTC has set system time to %02d/%02d/%d %02d:%02d:%02d",
+ month(tt), day(tt), year(tt), hour(tt), minute(tt), second(tt));
+ } else
+ ESP_LOGW(TAG, "System time was not synced");
+ }
+
+#ifdef TIME_SYNC_INTERVAL_RTC
+ setSyncProvider(&get_rtctime); // does not sync if callback function returns 0
+ if (timeStatus() != timeSet)
+ ESP_LOGI("Unable to sync with the RTC");
+ else
+ ESP_LOGI("RTC has set the system time");
+ setSyncInterval(TIME_SYNC_INTERVAL_RTC);
+#endif
+
+} // sync_rtctime;
+
+float get_rtctemp(void) {
+ // block i2c bus access
+ if (I2C_MUTEX_LOCK()) {
+ RtcTemperature temp = Rtc.GetTemperature();
+ I2C_MUTEX_UNLOCK(); // release i2c bus access
+ return temp.AsFloatDegC();
+ } // while
+ return 0;
+} // get_rtc()
+
+#endif // HAS_RTC
\ No newline at end of file