Merge branch 'development' into master

This commit is contained in:
Verkehrsrot 2019-01-28 00:46:49 +01:00 committed by GitHub
commit 696e46ce20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 553 additions and 88 deletions

View File

@ -28,7 +28,7 @@ This can all be done with a single small and cheap ESP32 board for less than $20
*LoRa & SPI*: *LoRa & SPI*:
- Heltec: LoRa-32 - 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 - Pycom: LoPy, LoPy4, FiPy
- WeMos: LoLin32 + [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora), - WeMos: LoLin32 + [LoraNode32 shield](https://github.com/hallard/LoLin32-Lora),
LoLin32lite + [LoraNode32-Lite shield](https://github.com/hallard/LoLin32-Lite-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 - Battery voltage monitoring
- GPS (Generic serial NMEA, or Quectel L76 I2C) - GPS (Generic serial NMEA, or Quectel L76 I2C)
- Environmental sensor (Bosch BME680 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).<br> 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> 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>
@ -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 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 # Building
Use <A HREF="https://platformio.org/">PlatformIO</A> with your preferred IDE for development and building this code. Make sure you have latest PlatformIO version. Use <A HREF="https://platformio.org/">PlatformIO</A> with your preferred IDE for development and building this code. Make sure you have latest PlatformIO version.

View File

@ -11,6 +11,11 @@
#include "bme680mems.h" #include "bme680mems.h"
#endif #endif
// Needed for RTC time sync if RTC present on board
#ifdef HAS_RTC
#include "rtctime.h"
#endif
void doHousekeeping(void); void doHousekeeping(void);
void do_timesync(void); void do_timesync(void);
uint64_t uptime(void); uint64_t uptime(void);

View File

@ -9,7 +9,6 @@
#include <array> #include <array>
#include <algorithm> #include <algorithm>
#include "mallocator.h" #include "mallocator.h"
//#include "inc/bsec_datatypes.h"
#include "../lib/Bosch-BSEC/src/inc/bsec_datatypes.h" #include "../lib/Bosch-BSEC/src/inc/bsec_datatypes.h"
// sniffing types // sniffing types
@ -36,6 +35,10 @@
#define BLE_MODE (0x40) #define BLE_MODE (0x40)
#define SCREEN_MODE (0x80) #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 // Struct holding devices's runtime configuration
typedef struct { typedef struct {
uint8_t lorasf; // 7-12, lora spreadfactor uint8_t lorasf; // 7-12, lora spreadfactor
@ -137,4 +140,8 @@ extern TaskHandle_t irqHandlerTask, wifiSwitchTask;
#include "bme680mems.h" #include "bme680mems.h"
#endif #endif
#ifdef HAS_IF482
#include "if482.h"
#endif
#endif #endif

View File

@ -2,7 +2,7 @@
#define _GPSREAD_H #define _GPSREAD_H
#include <TinyGPS++.h> // library for parsing NMEA data #include <TinyGPS++.h> // library for parsing NMEA data
#include <TimeLib.h> #include <Time.h>
#ifdef GPS_I2C // Needed for reading from I2C Bus #ifdef GPS_I2C // Needed for reading from I2C Bus
#include <Wire.h> #include <Wire.h>

13
include/if482.h Normal file
View File

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

View File

@ -17,6 +17,11 @@
#include <Wire.h> #include <Wire.h>
#endif #endif
// Needed for RTC time sync if RTC present on board
#ifdef HAS_RTC
#include "rtctime.h"
#endif
extern QueueHandle_t LoraSendQueue; extern QueueHandle_t LoraSendQueue;
void onEvent(ev_t ev); void onEvent(ev_t ev);

32
include/rtctime.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef _RTCTIME_H
#define _RTCTIME_H
#include "globals.h"
#include <Time.h>
#include <Timezone.h>
#include <Wire.h> // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>
#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<TwoWire> 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

View File

@ -30,7 +30,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
[common] [common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c" ; 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! ; 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 ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
debug_level = 3 debug_level = 3
@ -45,11 +45,13 @@ monitor_speed = 115200
lib_deps_lora = lib_deps_lora =
MCCI LoRaWAN LMIC library@^2.3.1 MCCI LoRaWAN LMIC library@^2.3.1
lib_deps_display = lib_deps_display =
U8g2@>=2.25.0 U8g2@>=2.25.5
lib_deps_rgbled = lib_deps_rgbled =
SmartLeds@>=1.1.3 SmartLeds@>=1.1.3
lib_deps_gps = lib_deps_gps =
TinyGPSPlus@>=1.0.2 TinyGPSPlus@>=1.0.2
lib_deps_rtc =
RTC@^2.3.0
lib_deps_basic = lib_deps_basic =
ArduinoJson@^5.13.1 ArduinoJson@^5.13.1
Time@>=1.5 Time@>=1.5
@ -59,6 +61,7 @@ lib_deps_all =
${common.lib_deps_display} ${common.lib_deps_display}
${common.lib_deps_rgbled} ${common.lib_deps_rgbled}
${common.lib_deps_gps} ${common.lib_deps_gps}
${common.lib_deps_rtc}
build_flags_basic = build_flags_basic =
-include "src/hal/${PIOENV}.h" -include "src/hal/${PIOENV}.h"
-include "src/paxcounter.conf" -include "src/paxcounter.conf"
@ -212,6 +215,7 @@ lib_deps =
${common.lib_deps_basic} ${common.lib_deps_basic}
${common.lib_deps_lora} ${common.lib_deps_lora}
${common.lib_deps_display} ${common.lib_deps_display}
${common.lib_deps_rtc}
build_flags = build_flags =
${common.build_flags_basic} ${common.build_flags_basic}
upload_protocol = ${common.upload_protocol} upload_protocol = ${common.upload_protocol}

View File

@ -1,5 +1,3 @@
#ifdef BLECOUNTER
/* code snippets taken from /* code snippets taken from
https://github.com/nkolban/esp32-snippets/tree/master/BLE/scanner https://github.com/nkolban/esp32-snippets/tree/master/BLE/scanner
*/ */
@ -235,8 +233,6 @@ esp_err_t register_ble_callback(void) {
} // register_ble_callback } // register_ble_callback
#endif // BLECOUNTER
void start_BLEscan(void) { void start_BLEscan(void) {
#ifdef BLECOUNTER #ifdef BLECOUNTER
ESP_LOGI(TAG, "Initializing bluetooth scanner ..."); ESP_LOGI(TAG, "Initializing bluetooth scanner ...");

View File

@ -32,8 +32,7 @@ int bme_init(void) {
// return = 0 -> error / return = 1 -> success // return = 0 -> error / return = 1 -> success
// block i2c bus access // block i2c bus access
if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == if (I2C_MUTEX_LOCK()) {
pdTRUE) {
Wire.begin(HAS_BME); Wire.begin(HAS_BME);
iaqSensor.begin(BME_ADDR, Wire); iaqSensor.begin(BME_ADDR, Wire);
@ -62,17 +61,16 @@ int bme_init(void) {
ESP_LOGE(TAG, "BSEC subscription error"); ESP_LOGE(TAG, "BSEC subscription error");
goto error; goto error;
} }
} else { } else {
ESP_LOGE(TAG, "I2c bus busy - BME680 initialization error"); ESP_LOGE(TAG, "I2c bus busy - BME680 initialization error");
goto error; goto error;
} }
xSemaphoreGive(I2Caccess); // release i2c bus access I2C_MUTEX_UNLOCK(); // release i2c bus access
return 1; return 1;
error: error:
xSemaphoreGive(I2Caccess); // release i2c bus access I2C_MUTEX_UNLOCK(); // release i2c bus access
return 0; return 0;
} // bme_init() } // bme_init()
@ -106,9 +104,9 @@ void bme_loop(void *pvParameters) {
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
#ifdef HAS_BME #ifdef HAS_BME
while (1) {
// block i2c bus access // block i2c bus access
while (xSemaphoreTake(I2Caccess, portMAX_DELAY) == pdTRUE) { if (I2C_MUTEX_LOCK()) {
if (iaqSensor.run()) { // If new data is available if (iaqSensor.run()) { // If new data is available
bme_status.raw_temperature = iaqSensor.rawTemperature; bme_status.raw_temperature = iaqSensor.rawTemperature;
bme_status.raw_humidity = iaqSensor.rawHumidity; bme_status.raw_humidity = iaqSensor.rawHumidity;
@ -121,9 +119,9 @@ void bme_loop(void *pvParameters) {
bme_status.gas = iaqSensor.gasResistance; bme_status.gas = iaqSensor.gasResistance;
updateState(); updateState();
} }
xSemaphoreGive(I2Caccess); // release i2c bus access I2C_MUTEX_UNLOCK();
}
} // while }
#endif #endif
ESP_LOGE(TAG, "BME task ended"); ESP_LOGE(TAG, "BME task ended");
vTaskDelete(BmeTask); // should never be reached vTaskDelete(BmeTask); // should never be reached

View File

@ -123,25 +123,30 @@ void reset_counters() {
void do_timesync() { void do_timesync() {
#ifdef TIME_SYNC_INTERVAL #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 #ifdef HAS_GPS
if (gps.time.isValid()) { if (gps.time.isValid()) {
setTime(gps.time.hour(), gps.time.minute(), gps.time.second(), setTime(gps.time.hour(), gps.time.minute(), gps.time.second(),
gps.date.day(), gps.date.month(), gps.date.year()); gps.date.day(), gps.date.month(), gps.date.year());
ESP_LOGI(TAG, "Time synced by GPS to %02d:%02d:%02d", hour(), minute(), // set RTC time to time source GPS, if RTC is present
second()); #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; return;
} else { } else {
ESP_LOGI(TAG, "No valid GPS time"); ESP_LOGI(TAG, "No valid GPS time");
} }
#endif // HAS_GPS
// sync time by LoRa Network if network supports DevTimeReq // set system time to time source LoRa Network, if network supports DevTimeReq
#ifdef LMIC_ENABLE_DeviceTimeReq #elif defined LMIC_ENABLE_DeviceTimeReq
// Schedule a network time request at the next possible time // Schedule a network time sync request at the next possible time
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime); LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
ESP_LOGI(TAG, "Network time request scheduled"); ESP_LOGI(TAG, "Network time request scheduled");
#endif #endif // HAS_GPS
#endif // TIME_SYNC_INTERVAL #endif // TIME_SYNC_INTERVAL
} // do_timesync() } // do_timesync()

View File

@ -1,5 +1,27 @@
#ifdef HAS_DISPLAY #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 // Basic Config
#include "globals.h" #include "globals.h"
#include <esp_spi_flash.h> // needed for reading ESP32 chip attributes #include <esp_spi_flash.h> // needed for reading ESP32 chip attributes
@ -19,6 +41,10 @@ const char lora_datarate[] = {"1211100908078CNA1211109C8C7C"};
const char lora_datarate[] = {"121110090807FSNA"}; const char lora_datarate[] = {"121110090807FSNA"};
#endif #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; uint8_t volatile DisplayState = 0;
// helper function, prints a hex key on display // helper function, prints a hex key on display
@ -99,8 +125,7 @@ void init_display(const char *Productname, const char *Version) {
void refreshtheDisplay() { void refreshtheDisplay() {
// block i2c bus access // block i2c bus access
if (xSemaphoreTake(I2Caccess, (DISPLAYREFRESH_MS / portTICK_PERIOD_MS)) == if (I2C_MUTEX_LOCK()) {
pdTRUE) {
// set display on/off according to current device configuration // set display on/off according to current device configuration
if (DisplayState != cfg.screenon) { if (DisplayState != cfg.screenon) {
@ -126,11 +151,13 @@ void refreshtheDisplay() {
// update Battery status (line 2) // update Battery status (line 2)
#ifdef HAS_BATTERY_PROBE #ifdef HAS_BATTERY_PROBE
u8x8.setCursor(0, 2); u8x8.setCursor(0, 2);
u8x8.printf("B:%.1fV", batt_voltage / 1000.0); u8x8.printf("B:%.2fV", batt_voltage / 1000.0);
#endif #endif
// update GPS status (line 2) // update GPS status (line 2)
#ifdef HAS_GPS #ifdef HAS_GPS
// have we ever got valid gps data?
if (gps.passedChecksum() > 0) {
u8x8.setCursor(9, 2); u8x8.setCursor(9, 2);
if (!gps.location.isValid()) // if no fix then display Sats value inverse if (!gps.location.isValid()) // if no fix then display Sats value inverse
{ {
@ -139,13 +166,14 @@ void refreshtheDisplay() {
u8x8.setInverseFont(0); u8x8.setInverseFont(0);
} else } else
u8x8.printf("Sats:%.2d", gps.satellites.value()); u8x8.printf("Sats:%.2d", gps.satellites.value());
}
#endif #endif
// update bluetooth counter + LoRa SF (line 3) // update bluetooth counter + LoRa SF (line 3)
#ifdef BLECOUNTER #ifdef BLECOUNTER
u8x8.setCursor(0, 3); u8x8.setCursor(0, 3);
if (cfg.blescan) if (cfg.blescan)
u8x8.printf("BLTH:%-4d", macs_ble); u8x8.printf("BLTH:%-5d", macs_ble);
else else
u8x8.printf("%s", "BLTH:off"); u8x8.printf("%s", "BLTH:off");
#endif #endif
@ -163,7 +191,7 @@ void refreshtheDisplay() {
// update wifi counter + channel display (line 4) // update wifi counter + channel display (line 4)
u8x8.setCursor(0, 4); u8x8.setCursor(0, 4);
u8x8.printf("WIFI:%-4d", macs_wifi); u8x8.printf("WIFI:%-5d", macs_wifi);
u8x8.setCursor(11, 4); u8x8.setCursor(11, 4);
u8x8.printf("ch:%02d", channel); u8x8.printf("ch:%02d", channel);
@ -174,9 +202,18 @@ void refreshtheDisplay() {
u8x8.printf("%4dKB", getFreeRAM() / 1024); u8x8.printf("%4dKB", getFreeRAM() / 1024);
#ifdef HAS_LORA #ifdef HAS_LORA
// update LoRa status display (line 6)
u8x8.setCursor(0, 6); u8x8.setCursor(0, 6);
#ifndef HAS_RTC
// update LoRa status display (line 6)
u8x8.printf("%-16s", display_line6); 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) // update LMiC event display (line 7)
u8x8.setCursor(0, 7); u8x8.setCursor(0, 7);
@ -193,7 +230,7 @@ void refreshtheDisplay() {
#endif // HAS_LORA #endif // HAS_LORA
xSemaphoreGive(I2Caccess); // release i2c bus access I2C_MUTEX_UNLOCK(); // release i2c bus access
} }
} // refreshDisplay() } // refreshDisplay()

View File

@ -10,7 +10,7 @@ gpsStatus_t gps_status;
TaskHandle_t GpsTask; TaskHandle_t GpsTask;
#ifdef GPS_SERIAL #ifdef GPS_SERIAL
HardwareSerial GPS_Serial(1); HardwareSerial GPS_Serial(1); // use UART #1
#endif #endif
// initialize and configure GPS // initialize and configure GPS

View File

@ -6,6 +6,7 @@
#include <stdint.h> #include <stdint.h>
// Hardware related definitions for generic ESP32 boards // 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_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 #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 HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
//#define DISPLAY_FLIP 1 // use if display is rotated //#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 BATT_FACTOR 2 // voltage divider 100k/100k on board
#define HAS_LED (21) // on board LED #define HAS_LED (21) // on board LED
@ -47,6 +48,9 @@
#define MY_OLED_SCL (15) #define MY_OLED_SCL (15)
#define MY_OLED_RST (16) #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 // Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5) #define LORA_SCK (5)
#define LORA_CS (18) #define LORA_CS (18)

View File

@ -24,13 +24,13 @@
// enable only if device has these sensors, otherwise comment these lines // enable only if device has these sensors, otherwise comment these lines
// BME680 sensor on I2C bus // BME680 sensor on I2C bus
//#define HAS_BME GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL //#define HAS_BME SDA, SCL
//#define BME_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND //#define BME_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !!
// display (if connected) // display (if connected)
//#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C //#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
//#define MY_OLED_SDA (21) //#define MY_OLED_SDA SDA
//#define MY_OLED_SCL (22) //#define MY_OLED_SCL SCL
//#define MY_OLED_RST U8X8_PIN_NONE //#define MY_OLED_RST U8X8_PIN_NONE
//#define DISPLAY_FLIP 1 // use if display is rotated //#define DISPLAY_FLIP 1 // use if display is rotated

View File

@ -9,9 +9,9 @@
#define CFG_sx1276_radio 1 // HPD13A LoRa SoC #define CFG_sx1276_radio 1 // HPD13A LoRa SoC
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_LED NOT_A_PIN // green on board LED #define HAS_LED NOT_A_PIN // green on board LED is useless, is GPIO25, which switches power for Lora+Display
//#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW #define HAS_LOWPOWER_SWITCH GPIO_NUM_25 // switches power for LoRa chip + display (0 = off / 1 = on)
#define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL // uses GPIO7 #define HAS_BATTERY_PROBE ADC1_GPIO35_CHANNEL
#define BATT_FACTOR 2 // voltage divider 100k/100k on board #define BATT_FACTOR 2 // voltage divider 100k/100k on board
#define HAS_BUTTON GPIO_NUM_36 // on board button (next to reset) #define HAS_BUTTON GPIO_NUM_36 // on board button (next to reset)
@ -20,6 +20,13 @@
#define MY_OLED_SCL (22) #define MY_OLED_SCL (22)
#define MY_OLED_RST U8X8_PIN_NONE #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 // Pins for LORA chip SPI interface, reset line and interrupt lines
#define LORA_SCK (5) #define LORA_SCK (5)
#define LORA_CS (18) #define LORA_CS (18)

View File

@ -15,7 +15,7 @@
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
#define HAS_LED (25) // green on board LED #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 #define BATT_FACTOR 2 // voltage divider 100k/100k on board
// Pins for I2C interface of OLED Display // Pins for I2C interface of OLED Display

View File

@ -18,7 +18,7 @@
#define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C #define HAS_DISPLAY U8X8_SSD1306_128X64_NONAME_HW_I2C
//#define DISPLAY_FLIP 1 // rotated display //#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 //#define BATT_FACTOR 2 // voltage divider 100k/100k on board
// Pins for I2C interface of OLED Display // Pins for I2C interface of OLED Display

160
src/if482.cpp Normal file
View File

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

View File

@ -455,6 +455,12 @@ void user_request_network_time_callback(void *pVoidUserUTCTime,
// Update system time with time read from the network // Update system time with time read from the network
setTime(*pUserUTCTime); setTime(*pUserUTCTime);
ESP_LOGI(TAG, "Time synced by LoRa network to %02d:%02d:%02d", hour(), #ifdef HAS_RTC
minute(), second()); 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));
} }

View File

@ -29,6 +29,7 @@ Task Core Prio Purpose
==================================================================================== ====================================================================================
wifiloop 0 4 rotates wifi channels wifiloop 0 4 rotates wifi channels
ledloop 0 3 blinks LEDs ledloop 0 3 blinks LEDs
if482loop 1 3 serial feed of IF482 time telegrams
spiloop 0 2 reads/writes data on spi interface spiloop 0 2 reads/writes data on spi interface
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer 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) (irqhandler, bmeloop)
ESP32 hardware timers ESP32 hardware timers
========================== ================================
0 Trigger display refresh 0 triggers display refresh
1 Trigger Wifi channel switch 1 triggers Wifi channel switch
2 Trigger send payload cycle 2 triggers send payload cycle
3 Trigger housekeeping cycle 3 triggers housekeeping cycle
RTC hardware timer (if present)
================================
triggers IF482 clock generator
*/ */
@ -147,6 +152,13 @@ void setup() {
strcat_P(features, " PSRAM"); strcat_P(features, " PSRAM");
#endif #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 // initialize leds
#if (HAS_LED != NOT_A_PIN) #if (HAS_LED != NOT_A_PIN)
pinMode(HAS_LED, OUTPUT); pinMode(HAS_LED, OUTPUT);
@ -161,7 +173,7 @@ void setup() {
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
// start led loop // start led loop
ESP_LOGI(TAG, "Starting LEDloop..."); ESP_LOGI(TAG, "Starting LED Controller...");
xTaskCreatePinnedToCore(ledLoop, // task function xTaskCreatePinnedToCore(ledLoop, // task function
"ledloop", // name of task "ledloop", // name of task
1024, // stack size of task 1024, // stack size of task
@ -171,7 +183,26 @@ void setup() {
0); // CPU core 0); // CPU core
#endif #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 #ifdef HAS_ANTENNA_SWITCH
strcat_P(features, " ANT"); strcat_P(features, " ANT");
antenna_init(); antenna_init();
@ -233,7 +264,7 @@ void setup() {
#ifdef HAS_GPS #ifdef HAS_GPS
strcat_P(features, " GPS"); strcat_P(features, " GPS");
if (gps_init()) { if (gps_init()) {
ESP_LOGI(TAG, "Starting GPSloop..."); ESP_LOGI(TAG, "Starting GPS Feed...");
xTaskCreatePinnedToCore(gps_loop, // task function xTaskCreatePinnedToCore(gps_loop, // task function
"gpsloop", // name of task "gpsloop", // name of task
2048, // stack size of task 2048, // stack size of task
@ -312,7 +343,7 @@ void setup() {
ESP_LOGI(TAG, "Features:%s", features); ESP_LOGI(TAG, "Features:%s", features);
#ifdef HAS_LORA #ifdef HAS_LORA
// output LoRaWAN keys to console // output LoRaWAN keys to console
#ifdef VERBOSE #ifdef VERBOSE
showLoraKeys(); showLoraKeys();
#endif #endif
@ -327,7 +358,7 @@ void setup() {
get_salt(); // get new 16bit for salting hashes get_salt(); // get new 16bit for salting hashes
// start state machine // start state machine
ESP_LOGI(TAG, "Starting IRQ Handler..."); ESP_LOGI(TAG, "Starting Interrupt Handler...");
xTaskCreatePinnedToCore(irqHandler, // task function xTaskCreatePinnedToCore(irqHandler, // task function
"irqhandler", // name of task "irqhandler", // name of task
4096, // stack size of task 4096, // stack size of task
@ -346,11 +377,11 @@ void setup() {
&wifiSwitchTask, // task handle &wifiSwitchTask, // task handle
0); // CPU core 0); // CPU core
// initialize bme // initialize bme
#ifdef HAS_BME #ifdef HAS_BME
strcat_P(features, " BME"); strcat_P(features, " BME");
if (bme_init()) { if (bme_init()) {
ESP_LOGI(TAG, "Starting BMEloop..."); ESP_LOGI(TAG, "Starting Bluetooth sniffer...");
xTaskCreatePinnedToCore(bme_loop, // task function xTaskCreatePinnedToCore(bme_loop, // task function
"bmeloop", // name of task "bmeloop", // name of task
2048, // stack size of task 2048, // stack size of task
@ -361,6 +392,7 @@ void setup() {
} }
#endif #endif
assert(irqHandlerTask != NULL); // has interrupt handler task started?
// start timer triggered interrupts // start timer triggered interrupts
ESP_LOGI(TAG, "Starting Interrupts..."); ESP_LOGI(TAG, "Starting Interrupts...");
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
@ -370,7 +402,7 @@ void setup() {
timerAlarmEnable(homeCycle); timerAlarmEnable(homeCycle);
timerAlarmEnable(channelSwitch); timerAlarmEnable(channelSwitch);
// start button interrupt // start button interrupt
#ifdef HAS_BUTTON #ifdef HAS_BUTTON
#ifdef BUTTON_PULLUP #ifdef BUTTON_PULLUP
attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING); attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING);
@ -379,6 +411,14 @@ void setup() {
#endif #endif
#endif // HAS_BUTTON #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() } // setup()
void loop() { void loop() {

View File

@ -81,8 +81,13 @@
#define OTA_MIN_BATT 3600 // minimum battery level for OTA [millivolt] #define OTA_MIN_BATT 3600 // minimum battery level for OTA [millivolt]
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds] #define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
// setting for syncing time of node // settings for syncing time of node and external time sources
//#define TIME_SYNC_INTERVAL 60 // sync time each ... minutes [default = 60], comment out means off #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 // LMIC settings
// moved to src/lmic_config.h // moved to src/lmic_config.h

137
src/rtctime.cpp Normal file
View File

@ -0,0 +1,137 @@
#ifdef HAS_RTC
#include "rtctime.h"
// Local logging tag
static const char TAG[] = "main";
RtcDS3231<TwoWire> 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