commit
0580842a5f
@ -18,6 +18,15 @@
|
||||
#include "display.h"
|
||||
#endif
|
||||
|
||||
#if (HAS_SDS011)
|
||||
#include "sds011read.h"
|
||||
#endif
|
||||
|
||||
#if (HAS_SDCARD)
|
||||
#include "sdcard.h"
|
||||
#endif
|
||||
|
||||
|
||||
extern Ticker housekeeper;
|
||||
|
||||
void housekeeping(void);
|
||||
|
@ -61,6 +61,62 @@ public:
|
||||
void addTime(time_t value);
|
||||
void addPM10(float value);
|
||||
void addPM25(float value);
|
||||
private:
|
||||
void addChars( char* string, int len);
|
||||
|
||||
#if (PAYLOAD_ENCODER == 1) // format plain
|
||||
|
||||
private:
|
||||
uint8_t *buffer;
|
||||
uint8_t cursor;
|
||||
|
||||
#elif (PAYLOAD_ENCODER == 2) // format packed
|
||||
|
||||
private:
|
||||
uint8_t *buffer;
|
||||
uint8_t cursor;
|
||||
void uintToBytes(uint64_t i, uint8_t byteSize);
|
||||
void writeUptime(uint64_t unixtime);
|
||||
void writeLatLng(double latitude, double longitude);
|
||||
void writeUint64(uint64_t i);
|
||||
void writeUint32(uint32_t i);
|
||||
void writeUint16(uint16_t i);
|
||||
void writeUint8(uint8_t i);
|
||||
void writeFloat(float value);
|
||||
void writeUFloat(float value);
|
||||
void writePressure(float value);
|
||||
void writeVersion(char *version);
|
||||
void writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f, bool g,
|
||||
bool h);
|
||||
|
||||
#elif ((PAYLOAD_ENCODER == 3) || (PAYLOAD_ENCODER == 4)) // format cayenne lpp
|
||||
|
||||
private:
|
||||
uint8_t *buffer;
|
||||
uint8_t maxsize;
|
||||
uint8_t cursor;
|
||||
|
||||
#else
|
||||
#error No valid payload converter defined!
|
||||
#endif
|
||||
};
|
||||
|
||||
extern PayloadConvert payload;
|
||||
|
||||
#endif // _PAYLOAD_H_
|
||||
void addCount(uint16_t value, uint8_t sniffytpe);
|
||||
void addConfig(configData_t value);
|
||||
void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem,
|
||||
uint8_t reset1, uint8_t reset2);
|
||||
void addAlarm(int8_t rssi, uint8_t message);
|
||||
void addVoltage(uint16_t value);
|
||||
void addGPS(gpsStatus_t value);
|
||||
void addBME(bmeStatus_t value);
|
||||
void addButton(uint8_t value);
|
||||
void addSensor(uint8_t[]);
|
||||
void addTime(time_t value);
|
||||
void addPM10(float value);
|
||||
void addPM25(float value);
|
||||
void addChars( char* string, int len);
|
||||
|
||||
#if (PAYLOAD_ENCODER == 1) // format plain
|
||||
|
@ -10,7 +10,7 @@
|
||||
#define SDCARD_FILE_NAME "paxcount.%02d"
|
||||
#define SDCARD_FILE_HEADER "date, time, wifi, bluet"
|
||||
|
||||
bool sdcardInit( void );
|
||||
bool sdcard_init( void );
|
||||
void sdcardWriteData( uint16_t, uint16_t);
|
||||
|
||||
#endif
|
@ -11,5 +11,7 @@
|
||||
|
||||
bool sds011_init();
|
||||
void sds011_loop();
|
||||
void sds011_sleep(void);
|
||||
void sds011_wakeup(void);
|
||||
|
||||
#endif // _SDS011READ_H
|
||||
|
114
src/cyclic.cpp
114
src/cyclic.cpp
@ -9,8 +9,14 @@ static const char TAG[] = __FILE__;
|
||||
|
||||
Ticker housekeeper;
|
||||
|
||||
#if (HAS_SDS011)
|
||||
extern boolean isSDS011Active;
|
||||
#endif
|
||||
|
||||
void housekeeping() {
|
||||
static int counter = 0;
|
||||
xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL);
|
||||
ESP_LOGI( TAG, "in Housekeeping(): %d", counter++);
|
||||
}
|
||||
|
||||
// do all housekeeping
|
||||
@ -18,7 +24,6 @@ void doHousekeeping() {
|
||||
|
||||
// update uptime counter
|
||||
uptime();
|
||||
|
||||
// check if update mode trigger switch was set
|
||||
if (RTC_runmode == RUNMODE_UPDATE) {
|
||||
// check battery status if we can before doing ota
|
||||
@ -112,6 +117,113 @@ void doHousekeeping() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (HAS_SDS011)
|
||||
if ( isSDS011Active ) {
|
||||
ESP_LOGD(TAG, "SDS011: go to sleep");
|
||||
sds011_loop();
|
||||
}
|
||||
else {
|
||||
ESP_LOGD(TAG, "SDS011: wakeup");
|
||||
sds011_wakeup();
|
||||
}
|
||||
#endif
|
||||
|
||||
} // doHousekeeping()
|
||||
|
||||
// uptime counter 64bit to prevent millis() rollover after 49 days
|
||||
uint64_t uptime() {
|
||||
static uint32_t low32, high32;
|
||||
uint32_t new_low32 = millis();
|
||||
if (new_low32 < low32)
|
||||
high32++;
|
||||
low32 = new_low32;
|
||||
return (uint64_t)high32 << 32 | low32;
|
||||
}
|
||||
|
||||
uint32_t getFreeRAM() {
|
||||
#ifndef BOARD_HAS_PSRAM
|
||||
return ESP.getFreeHeap();
|
||||
#else
|
||||
return ESP.getFreePsram();
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset_counters() {
|
||||
#if ((WIFICOUNTER) || (BLECOUNTER))
|
||||
macs.clear(); // clear all macs container
|
||||
macs_total = 0; // reset all counters
|
||||
macs_wifi = 0;
|
||||
macs_ble = 0;
|
||||
#ifdef HAS_DISPLAY
|
||||
oledPlotCurve(0, true);
|
||||
#endif
|
||||
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (defined HAS_DCF77 || defined HAS_IF482)
|
||||
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
|
||||
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
|
||||
#endif
|
||||
|
||||
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
|
||||
ESP_LOGD(TAG, "LEDloop %d bytes left | Taskstate = %d",
|
||||
uxTaskGetStackHighWaterMark(ledLoopTask),
|
||||
eTaskGetState(ledLoopTask));
|
||||
#endif
|
||||
|
||||
// read battery voltage into global variable
|
||||
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
|
||||
batt_voltage = read_voltage();
|
||||
if (batt_voltage == 0xffff)
|
||||
ESP_LOGI(TAG, "Battery: external power");
|
||||
else
|
||||
ESP_LOGI(TAG, "Battery: %dmV", batt_voltage);
|
||||
#ifdef HAS_PMU
|
||||
AXP192_showstatus();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// display BME680/280 sensor data
|
||||
#if (HAS_BME)
|
||||
#ifdef HAS_BME680
|
||||
ESP_LOGI(TAG, "BME680 Temp: %.2f°C | IAQ: %.2f | IAQacc: %d",
|
||||
bme_status.temperature, bme_status.iaq, bme_status.iaq_accuracy);
|
||||
#elif defined HAS_BME280
|
||||
ESP_LOGI(TAG, "BME280 Temp: %.2f°C | Humidity: %.2f | Pressure: %.0f",
|
||||
bme_status.temperature, bme_status.humidity, bme_status.pressure);
|
||||
#elif defined HAS_BMP180
|
||||
ESP_LOGI(TAG, "BMP180 Temp: %.2f°C | Pressure: %.0f", bme_status.temperature,
|
||||
bme_status.pressure);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// check free heap memory
|
||||
if (ESP.getMinFreeHeap() <= MEM_LOW) {
|
||||
ESP_LOGI(TAG,
|
||||
"Memory full, counter cleared (heap low water mark = %d Bytes / "
|
||||
"free heap = %d bytes)",
|
||||
ESP.getMinFreeHeap(), ESP.getFreeHeap());
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
|
||||
if (ESP.getMinFreeHeap() <= MEM_LOW) // check again
|
||||
do_reset(true); // memory leak, reset device
|
||||
}
|
||||
|
||||
// check free PSRAM memory
|
||||
#ifdef BOARD_HAS_PSRAM
|
||||
if (ESP.getMinFreePsram() <= MEM_LOW) {
|
||||
ESP_LOGI(TAG, "PSRAM full, counter cleared");
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
|
||||
if (ESP.getMinFreePsram() <= MEM_LOW) // check again
|
||||
do_reset(true); // memory leak, reset device
|
||||
}
|
||||
#endif
|
||||
|
||||
} // doHousekeeping()
|
||||
|
||||
// uptime counter 64bit to prevent millis() rollover after 49 days
|
||||
|
437
src/main.cpp
437
src/main.cpp
@ -323,6 +323,443 @@ void setup() {
|
||||
assert(spi_init() == ESP_OK);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
if (sdcard_init())
|
||||
strcat_P(features, " SD");
|
||||
#endif
|
||||
|
||||
#if (HAS_SDS011)
|
||||
ESP_LOGI(TAG, "init fine-dust-sensor");
|
||||
if ( sds011_init() )
|
||||
strcat_P(features, " SDS");
|
||||
#endif
|
||||
|
||||
#if (VENDORFILTER)
|
||||
strcat_P(features, " FILTER");
|
||||
#endif
|
||||
|
||||
// initialize matrix display
|
||||
#ifdef HAS_MATRIX_DISPLAY
|
||||
strcat_P(features, " LED_MATRIX");
|
||||
MatrixDisplayIsOn = cfg.screenon;
|
||||
init_matrix_display(); // note: blocking call
|
||||
#endif
|
||||
|
||||
// show payload encoder
|
||||
#if PAYLOAD_ENCODER == 1
|
||||
strcat_P(features, " PLAIN");
|
||||
#elif PAYLOAD_ENCODER == 2
|
||||
strcat_P(features, " PACKED");
|
||||
#elif PAYLOAD_ENCODER == 3
|
||||
strcat_P(features, " LPPDYN");
|
||||
#elif PAYLOAD_ENCODER == 4
|
||||
strcat_P(features, " LPPPKD");
|
||||
#endif
|
||||
|
||||
// initialize RTC
|
||||
#ifdef HAS_RTC
|
||||
strcat_P(features, " RTC");
|
||||
assert(rtc_init());
|
||||
#endif
|
||||
|
||||
#if defined HAS_DCF77
|
||||
strcat_P(features, " DCF77");
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482
|
||||
strcat_P(features, " IF482");
|
||||
#endif
|
||||
|
||||
#if (WIFICOUNTER)
|
||||
strcat_P(features, " WIFI");
|
||||
// start wifi in monitor mode and start channel rotation timer
|
||||
ESP_LOGI(TAG, "Starting Wifi...");
|
||||
wifi_sniffer_init();
|
||||
#else
|
||||
// switch off wifi
|
||||
esp_wifi_deinit();
|
||||
#endif
|
||||
|
||||
// initialize salt value using esp_random() called by random() in
|
||||
// arduino-esp32 core. Note: do this *after* wifi has started, since
|
||||
// function gets it's seed from RF noise
|
||||
get_salt(); // get new 16bit for salting hashes
|
||||
|
||||
// start state machine
|
||||
ESP_LOGI(TAG, "Starting Interrupt Handler...");
|
||||
xTaskCreatePinnedToCore(irqHandler, // task function
|
||||
"irqhandler", // name of task
|
||||
4096, // stack size of task
|
||||
(void *)1, // parameter of the task
|
||||
2, // priority of the task
|
||||
&irqHandlerTask, // task handle
|
||||
1); // CPU core
|
||||
|
||||
// initialize BME sensor (BME280/BME680)
|
||||
#if (HAS_BME)
|
||||
#ifdef HAS_BME680
|
||||
strcat_P(features, " BME680");
|
||||
#elif defined HAS_BME280
|
||||
strcat_P(features, " BME280");
|
||||
#elif defined HAS_BMP180
|
||||
strcat_P(features, " BMP180");
|
||||
#endif
|
||||
if (bme_init())
|
||||
ESP_LOGI(TAG, "Starting BME sensor...");
|
||||
#endif
|
||||
|
||||
// starting timers and interrupts
|
||||
assert(irqHandlerTask != NULL); // has interrupt handler task started?
|
||||
ESP_LOGI(TAG, "Starting Timers...");
|
||||
|
||||
// display interrupt
|
||||
#ifdef HAS_DISPLAY
|
||||
// https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/
|
||||
// prescaler 80 -> divides 80 MHz CPU freq to 1 MHz, timer 0, count up
|
||||
displayIRQ = timerBegin(0, 80, true);
|
||||
timerAttachInterrupt(displayIRQ, &DisplayIRQ, true);
|
||||
timerAlarmWrite(displayIRQ, DISPLAYREFRESH_MS * 1000, true);
|
||||
timerAlarmEnable(displayIRQ);
|
||||
#endif
|
||||
|
||||
// LED Matrix display interrupt
|
||||
#ifdef HAS_MATRIX_DISPLAY
|
||||
// https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/
|
||||
// prescaler 80 -> divides 80 MHz CPU freq to 1 MHz, timer 3, count up
|
||||
matrixDisplayIRQ = timerBegin(3, 80, true);
|
||||
timerAttachInterrupt(matrixDisplayIRQ, &MatrixDisplayIRQ, true);
|
||||
timerAlarmWrite(matrixDisplayIRQ, MATRIX_DISPLAY_SCAN_US, true);
|
||||
timerAlarmEnable(matrixDisplayIRQ);
|
||||
#endif
|
||||
|
||||
// initialize button
|
||||
#ifdef HAS_BUTTON
|
||||
strcat_P(features, " BTN_");
|
||||
#ifdef BUTTON_PULLUP
|
||||
strcat_P(features, "PU");
|
||||
#else
|
||||
strcat_P(features, "PD");
|
||||
#endif // BUTTON_PULLUP
|
||||
button_init(HAS_BUTTON);
|
||||
#endif // HAS_BUTTON
|
||||
|
||||
// cyclic function interrupts
|
||||
sendcycler.attach(SENDCYCLE * 2, sendcycle);
|
||||
housekeeper.attach(HOMECYCLE, housekeeping);
|
||||
|
||||
#if (TIME_SYNC_INTERVAL)
|
||||
|
||||
#if (!(TIME_SYNC_LORAWAN) && !(TIME_SYNC_LORASERVER) && !defined HAS_GPS && \
|
||||
!defined HAS_RTC)
|
||||
#warning you did not specify a time source, time will not be synched
|
||||
#endif
|
||||
|
||||
// initialize gps time
|
||||
#if (HAS_GPS)
|
||||
fetch_gpsTime();
|
||||
#endif
|
||||
|
||||
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||
ESP_LOGI(TAG, "Starting Clock Controller...");
|
||||
clock_init();
|
||||
#endif
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
timesync_init(); // create loraserver time sync task
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Starting Timekeeper...");
|
||||
assert(timepulse_init()); // setup pps timepulse
|
||||
timepulse_start(); // starts pps and cyclic time sync
|
||||
|
||||
#endif // TIME_SYNC_INTERVAL
|
||||
|
||||
// show compiled features
|
||||
ESP_LOGI(TAG, "Features:%s", features);
|
||||
|
||||
// set runmode to normal
|
||||
RTC_runmode = RUNMODE_NORMAL;
|
||||
|
||||
vTaskDelete(NULL);
|
||||
|
||||
} // setup()
|
||||
|
||||
void loop() { vTaskDelete(NULL); }
|
||||
3 MatrixDisplayIRQ -> matrix mux cycle -> 0,5ms (MATRIX_DISPLAY_SCAN_US)
|
||||
|
||||
|
||||
// Interrupt routines
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
fired by hardware
|
||||
DisplayIRQ -> esp32 timer 0 -> irqHandlerTask (Core 1)
|
||||
CLOCKIRQ -> esp32 timer 1 -> ClockTask (Core 1)
|
||||
ButtonIRQ -> external gpio -> irqHandlerTask (Core 1)
|
||||
PMUIRQ -> PMU chip gpio -> irqHandlerTask (Core 1)
|
||||
|
||||
fired by software (Ticker.h)
|
||||
TIMESYNC_IRQ -> timeSync() -> irqHandlerTask (Core 1)
|
||||
CYCLIC_IRQ -> housekeeping() -> irqHandlerTask (Core 1)
|
||||
SENDCYCLE_IRQ -> sendcycle() -> irqHandlerTask (Core 1)
|
||||
BME_IRQ -> bmecycle() -> irqHandlerTask (Core 1)
|
||||
|
||||
|
||||
// External RTC timer (if present)
|
||||
-------------------------------------------------------------------------------
|
||||
triggers pps 1 sec impulse
|
||||
|
||||
*/
|
||||
|
||||
// Basic Config
|
||||
#include "main.h"
|
||||
|
||||
configData_t cfg; // struct holds current device configuration
|
||||
char lmic_event_msg[LMIC_EVENTMSG_LEN]; // display buffer for LMIC event message
|
||||
uint8_t volatile channel = 0; // channel rotation counter
|
||||
uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
|
||||
batt_voltage = 0; // globals for display
|
||||
|
||||
hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL, *matrixDisplayIRQ = NULL;
|
||||
|
||||
TaskHandle_t irqHandlerTask = NULL, ClockTask = NULL;
|
||||
SemaphoreHandle_t I2Caccess;
|
||||
bool volatile TimePulseTick = false;
|
||||
time_t userUTCTime = 0;
|
||||
timesource_t timeSource = _unsynced;
|
||||
|
||||
// container holding unique MAC address hashes with Memory Alloctor using PSRAM,
|
||||
// if present
|
||||
std::set<uint16_t, std::less<uint16_t>, Mallocator<uint16_t>> macs;
|
||||
|
||||
// initialize payload encoder
|
||||
PayloadConvert payload(PAYLOAD_BUFFER_SIZE);
|
||||
|
||||
// set Time Zone for user setting from paxcounter.conf
|
||||
TimeChangeRule myDST = DAYLIGHT_TIME;
|
||||
TimeChangeRule mySTD = STANDARD_TIME;
|
||||
Timezone myTZ(myDST, mySTD);
|
||||
|
||||
// local Tag for logging
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
void setup() {
|
||||
|
||||
char features[100] = "";
|
||||
|
||||
// create some semaphores for syncing / mutexing tasks
|
||||
I2Caccess = xSemaphoreCreateMutex(); // for access management of i2c bus
|
||||
assert(I2Caccess != NULL);
|
||||
I2C_MUTEX_UNLOCK();
|
||||
|
||||
// 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;
|
||||
#endif
|
||||
|
||||
// setup debug output or silence device
|
||||
#if (VERBOSE)
|
||||
Serial.begin(115200);
|
||||
esp_log_level_set("*", ESP_LOG_VERBOSE);
|
||||
#else
|
||||
// mute logs completely by redirecting them to silence function
|
||||
esp_log_level_set("*", ESP_LOG_NONE);
|
||||
#endif
|
||||
|
||||
do_after_reset(rtc_get_reset_reason(0));
|
||||
|
||||
// print chip information on startup if in verbose mode after coldstart
|
||||
#if (VERBOSE)
|
||||
|
||||
if (RTC_runmode == RUNMODE_POWERCYCLE) {
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
ESP_LOGI(TAG,
|
||||
"This is ESP32 chip with %d CPU cores, WiFi%s%s, silicon revision "
|
||||
"%d, %dMB %s Flash",
|
||||
chip_info.cores,
|
||||
(chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : "",
|
||||
chip_info.revision, spi_flash_get_chip_size() / (1024 * 1024),
|
||||
(chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded"
|
||||
: "external");
|
||||
ESP_LOGI(TAG, "Internal Total heap %d, internal Free Heap %d",
|
||||
ESP.getHeapSize(), ESP.getFreeHeap());
|
||||
#ifdef BOARD_HAS_PSRAM
|
||||
ESP_LOGI(TAG, "SPIRam Total heap %d, SPIRam Free Heap %d",
|
||||
ESP.getPsramSize(), ESP.getFreePsram());
|
||||
#endif
|
||||
ESP_LOGI(TAG, "ChipRevision %d, Cpu Freq %d, SDK Version %s",
|
||||
ESP.getChipRevision(), ESP.getCpuFreqMHz(), ESP.getSdkVersion());
|
||||
ESP_LOGI(TAG, "Flash Size %d, Flash Speed %d", ESP.getFlashChipSize(),
|
||||
ESP.getFlashChipSpeed());
|
||||
ESP_LOGI(TAG, "Wifi/BT software coexist version %s",
|
||||
esp_coex_version_get());
|
||||
|
||||
#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",
|
||||
ARDUINO_LMIC_VERSION_GET_MAJOR(ARDUINO_LMIC_VERSION),
|
||||
ARDUINO_LMIC_VERSION_GET_MINOR(ARDUINO_LMIC_VERSION),
|
||||
ARDUINO_LMIC_VERSION_GET_PATCH(ARDUINO_LMIC_VERSION),
|
||||
ARDUINO_LMIC_VERSION_GET_LOCAL(ARDUINO_LMIC_VERSION));
|
||||
showLoraKeys();
|
||||
#endif // HAS_LORA
|
||||
|
||||
#if (HAS_GPS)
|
||||
ESP_LOGI(TAG, "TinyGPS+ version %s", TinyGPSPlus::libraryVersion());
|
||||
#endif
|
||||
}
|
||||
#endif // VERBOSE
|
||||
|
||||
// open i2c bus
|
||||
i2c_init();
|
||||
|
||||
// setup power on boards with power management logic
|
||||
#ifdef EXT_POWER_SW
|
||||
pinMode(EXT_POWER_SW, OUTPUT);
|
||||
digitalWrite(EXT_POWER_SW, EXT_POWER_ON);
|
||||
strcat_P(features, " VEXT");
|
||||
#endif
|
||||
#ifdef HAS_PMU
|
||||
AXP192_init();
|
||||
strcat_P(features, " PMU");
|
||||
#endif
|
||||
|
||||
// read (and initialize on first run) runtime settings from NVRAM
|
||||
loadConfig(); // includes initialize if necessary
|
||||
|
||||
// initialize display
|
||||
#ifdef HAS_DISPLAY
|
||||
strcat_P(features, " OLED");
|
||||
DisplayIsOn = cfg.screenon;
|
||||
// display verbose info only after a coldstart (note: blocking call!)
|
||||
init_display(RTC_runmode == RUNMODE_POWERCYCLE ? true : false);
|
||||
#endif
|
||||
|
||||
// scan i2c bus for devices
|
||||
i2c_scan();
|
||||
|
||||
#ifdef BOARD_HAS_PSRAM
|
||||
assert(psramFound());
|
||||
ESP_LOGI(TAG, "PSRAM found and initialized");
|
||||
strcat_P(features, " PSRAM");
|
||||
#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");
|
||||
|
||||
#ifdef LED_POWER_SW
|
||||
pinMode(LED_POWER_SW, OUTPUT);
|
||||
digitalWrite(LED_POWER_SW, LED_POWER_ON);
|
||||
#endif
|
||||
|
||||
#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");
|
||||
rgb_set_color(COLOR_PINK);
|
||||
#endif
|
||||
|
||||
#endif // HAS_LED
|
||||
|
||||
#if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
|
||||
// start led loop
|
||||
ESP_LOGI(TAG, "Starting LED Controller...");
|
||||
xTaskCreatePinnedToCore(ledLoop, // task function
|
||||
"ledloop", // name of task
|
||||
1024, // stack size of task
|
||||
(void *)1, // parameter of the task
|
||||
3, // priority of the task
|
||||
&ledLoopTask, // task handle
|
||||
0); // CPU core
|
||||
#endif
|
||||
|
||||
// initialize wifi antenna
|
||||
#ifdef HAS_ANTENNA_SWITCH
|
||||
strcat_P(features, " ANT");
|
||||
antenna_init();
|
||||
antenna_select(cfg.wifiant);
|
||||
#endif
|
||||
|
||||
// initialize battery status
|
||||
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
|
||||
strcat_P(features, " BATT");
|
||||
calibrate_voltage();
|
||||
batt_voltage = read_voltage();
|
||||
#endif
|
||||
|
||||
#if (USE_OTA)
|
||||
strcat_P(features, " OTA");
|
||||
// reboot to firmware update mode if ota trigger switch is set
|
||||
if (RTC_runmode == RUNMODE_UPDATE)
|
||||
start_ota_update();
|
||||
#endif
|
||||
|
||||
// start BLE scan callback if BLE function is enabled in NVRAM configuration
|
||||
// or switch off bluetooth, if not compiled
|
||||
#if (BLECOUNTER)
|
||||
strcat_P(features, " BLE");
|
||||
if (cfg.blescan) {
|
||||
ESP_LOGI(TAG, "Starting Bluetooth...");
|
||||
start_BLEscan();
|
||||
} else
|
||||
btStop();
|
||||
#else
|
||||
// remove bluetooth stack to gain more free memory
|
||||
btStop();
|
||||
ESP_ERROR_CHECK(esp_bt_mem_release(ESP_BT_MODE_BTDM));
|
||||
ESP_ERROR_CHECK(esp_coex_preference_set(
|
||||
ESP_COEX_PREFER_WIFI)); // configure Wifi/BT coexist lib
|
||||
#endif
|
||||
|
||||
// initialize gps
|
||||
#if (HAS_GPS)
|
||||
strcat_P(features, " GPS");
|
||||
if (gps_init()) {
|
||||
ESP_LOGI(TAG, "Starting GPS Feed...");
|
||||
xTaskCreatePinnedToCore(gps_loop, // task function
|
||||
"gpsloop", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)1, // parameter of the task
|
||||
1, // priority of the task
|
||||
&GpsTask, // task handle
|
||||
1); // CPU core
|
||||
}
|
||||
#endif
|
||||
|
||||
// initialize sensors
|
||||
#if (HAS_SENSORS)
|
||||
strcat_P(features, " SENS");
|
||||
sensor_init();
|
||||
#endif
|
||||
|
||||
// initialize LoRa
|
||||
#if (HAS_LORA)
|
||||
strcat_P(features, " LORA");
|
||||
// kick off join, except we come from sleep
|
||||
assert(lora_stack_init(RTC_runmode == RUNMODE_WAKEUP ? false : true) ==
|
||||
ESP_OK);
|
||||
#endif
|
||||
|
||||
// initialize SPI
|
||||
#ifdef HAS_SPI
|
||||
strcat_P(features, " SPI");
|
||||
assert(spi_init() == ESP_OK);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_SDCARD
|
||||
if (sdcardInit())
|
||||
strcat_P(features, " SD");
|
||||
|
468
src/payload.cpp
468
src/payload.cpp
@ -491,6 +491,474 @@ void PayloadConvert::addTime(time_t value) {
|
||||
}
|
||||
#endif // PAYLOAD_ENCODER
|
||||
|
||||
void PayloadConvert::addPM10( float value) {
|
||||
#if (HAS_SDS011)
|
||||
#if (PAYLOAD_ENCODER == 1) // plain
|
||||
char tempBuffer[10+1];
|
||||
sprintf( tempBuffer, ",%5.1f", value);
|
||||
addChars(tempBuffer, strlen(tempBuffer));
|
||||
#elif (PAYLOAD_ENCODER == 2 ) // packed
|
||||
writeUint16( (uint16_t) (value*10) );
|
||||
#elif (PAYLOAD_ENCODER == 3 ) // Cayenne LPP dynamic
|
||||
#error not implemented yet
|
||||
#elif (PAYLOAD_ENCODER == 4 ) // Cayenne LPP packed
|
||||
#error not implemented yet
|
||||
#endif
|
||||
#endif // HAS_SDS011
|
||||
}
|
||||
|
||||
void PayloadConvert::addPM25( float value) {
|
||||
#if (HAS_SDS011)
|
||||
#if (PAYLOAD_ENCODER == 1) // plain
|
||||
char tempBuffer[10+1];
|
||||
sprintf( tempBuffer, ",%5.1f", value);
|
||||
addChars(tempBuffer, strlen(tempBuffer));
|
||||
#elif (PAYLOAD_ENCODER == 2 ) // packed
|
||||
writeUint16( (uint16_t) (value*10) );
|
||||
#elif (PAYLOAD_ENCODER == 3 ) // Cayenne LPP dynamic
|
||||
#error not implemented yet
|
||||
#elif (PAYLOAD_ENCODER == 4 ) // Cayenne LPP packed
|
||||
#error not implemented yet
|
||||
#endif
|
||||
#endif // HAS_SDS011
|
||||
}
|
||||
|
||||
void PayloadConvert::addChars( char * string, int len) {
|
||||
for (int i=0; i < len; i++)
|
||||
addByte(string[i]);
|
||||
}
|
||||
|
||||
buffer[cursor++] = highByte(voltage);
|
||||
buffer[cursor++] = lowByte(voltage);
|
||||
buffer[cursor++] = (byte)((uptime & 0xFF00000000000000) >> 56);
|
||||
buffer[cursor++] = (byte)((uptime & 0x00FF000000000000) >> 48);
|
||||
buffer[cursor++] = (byte)((uptime & 0x0000FF0000000000) >> 40);
|
||||
buffer[cursor++] = (byte)((uptime & 0x000000FF00000000) >> 32);
|
||||
buffer[cursor++] = (byte)((uptime & 0x00000000FF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((uptime & 0x0000000000FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((uptime & 0x000000000000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((uptime & 0x00000000000000FF));
|
||||
buffer[cursor++] = (byte)(cputemp);
|
||||
buffer[cursor++] = (byte)((mem & 0xFF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((mem & 0x00FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((mem & 0x0000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((mem & 0x000000FF));
|
||||
buffer[cursor++] = (byte)(reset1);
|
||||
buffer[cursor++] = (byte)(reset2);
|
||||
}
|
||||
|
||||
void PayloadConvert::addGPS(gpsStatus_t value) {
|
||||
#if(HAS_GPS)
|
||||
buffer[cursor++] = (byte)((value.latitude & 0xFF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((value.latitude & 0x00FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((value.latitude & 0x0000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((value.latitude & 0x000000FF));
|
||||
buffer[cursor++] = (byte)((value.longitude & 0xFF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((value.longitude & 0x00FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((value.longitude & 0x0000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((value.longitude & 0x000000FF));
|
||||
#if (!PAYLOAD_OPENSENSEBOX)
|
||||
buffer[cursor++] = value.satellites;
|
||||
buffer[cursor++] = highByte(value.hdop);
|
||||
buffer[cursor++] = lowByte(value.hdop);
|
||||
buffer[cursor++] = highByte(value.altitude);
|
||||
buffer[cursor++] = lowByte(value.altitude);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addSensor(uint8_t buf[]) {
|
||||
#if(HAS_SENSORS)
|
||||
uint8_t length = buf[0];
|
||||
memcpy(buffer, buf + 1, length);
|
||||
cursor += length; // length of buffer
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addBME(bmeStatus_t value) {
|
||||
#if(HAS_BME)
|
||||
int16_t temperature = (int16_t)(value.temperature); // float -> int
|
||||
uint16_t humidity = (uint16_t)(value.humidity); // float -> int
|
||||
uint16_t pressure = (uint16_t)(value.pressure); // float -> int
|
||||
uint16_t iaq = (uint16_t)(value.iaq); // float -> int
|
||||
buffer[cursor++] = highByte(temperature);
|
||||
buffer[cursor++] = lowByte(temperature);
|
||||
buffer[cursor++] = highByte(pressure);
|
||||
buffer[cursor++] = lowByte(pressure);
|
||||
buffer[cursor++] = highByte(humidity);
|
||||
buffer[cursor++] = lowByte(humidity);
|
||||
buffer[cursor++] = highByte(iaq);
|
||||
buffer[cursor++] = lowByte(iaq);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addButton(uint8_t value) {
|
||||
#ifdef HAS_BUTTON
|
||||
buffer[cursor++] = value;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addTime(time_t value) {
|
||||
uint32_t time = (uint32_t)value;
|
||||
buffer[cursor++] = (byte)((time & 0xFF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((time & 0x00FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((time & 0x0000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((time & 0x000000FF));
|
||||
}
|
||||
|
||||
/* ---------------- packed format with LoRa serialization Encoder ----------
|
||||
*/
|
||||
// derived from
|
||||
// https://github.com/thesolarnomad/lora-serialization/blob/master/src/LoraEncoder.cpp
|
||||
|
||||
#elif (PAYLOAD_ENCODER == 2)
|
||||
|
||||
void PayloadConvert::addByte(uint8_t value) { writeUint8(value); }
|
||||
|
||||
void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) {
|
||||
writeUint16(value);
|
||||
}
|
||||
|
||||
void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) {
|
||||
writeUint8(rssi);
|
||||
writeUint8(msg);
|
||||
}
|
||||
|
||||
void PayloadConvert::addVoltage(uint16_t value) { writeUint16(value); }
|
||||
|
||||
void PayloadConvert::addConfig(configData_t value) {
|
||||
writeUint8(value.loradr);
|
||||
writeUint8(value.txpower);
|
||||
writeUint16(value.rssilimit);
|
||||
writeUint8(value.sendcycle);
|
||||
writeUint8(value.wifichancycle);
|
||||
writeUint8(value.blescantime);
|
||||
writeUint8(value.rgblum);
|
||||
writeBitmap(value.adrmode ? true : false, value.screensaver ? true : false,
|
||||
value.screenon ? true : false, value.countermode ? true : false,
|
||||
value.blescan ? true : false, value.wifiant ? true : false,
|
||||
value.vendorfilter ? true : false,
|
||||
value.monitormode ? true : false);
|
||||
writeBitmap(value.payloadmask && GPS_DATA ? true : false,
|
||||
value.payloadmask && ALARM_DATA ? true : false,
|
||||
value.payloadmask && MEMS_DATA ? true : false,
|
||||
value.payloadmask && COUNT_DATA ? true : false,
|
||||
value.payloadmask && SENSOR1_DATA ? true : false,
|
||||
value.payloadmask && SENSOR2_DATA ? true : false,
|
||||
value.payloadmask && SENSOR3_DATA ? true : false,
|
||||
value.payloadmask && BATT_DATA ? true : false);
|
||||
writeVersion(value.version);
|
||||
}
|
||||
|
||||
void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp,
|
||||
uint32_t mem, uint8_t reset1, uint8_t reset2) {
|
||||
writeUint16(voltage);
|
||||
writeUptime(uptime);
|
||||
writeUint8((byte)cputemp);
|
||||
writeUint32(mem);
|
||||
writeUint8(reset1);
|
||||
writeUint8(reset2);
|
||||
}
|
||||
|
||||
void PayloadConvert::addGPS(gpsStatus_t value) {
|
||||
#if(HAS_GPS)
|
||||
writeLatLng(value.latitude, value.longitude);
|
||||
#if (!PAYLOAD_OPENSENSEBOX)
|
||||
writeUint8(value.satellites);
|
||||
writeUint16(value.hdop);
|
||||
writeUint16(value.altitude);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addSensor(uint8_t buf[]) {
|
||||
#if(HAS_SENSORS)
|
||||
uint8_t length = buf[0];
|
||||
memcpy(buffer, buf + 1, length);
|
||||
cursor += length; // length of buffer
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addBME(bmeStatus_t value) {
|
||||
#if(HAS_BME)
|
||||
writeFloat(value.temperature);
|
||||
writePressure(value.pressure);
|
||||
writeUFloat(value.humidity);
|
||||
writeUFloat(value.iaq);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addButton(uint8_t value) {
|
||||
#ifdef HAS_BUTTON
|
||||
writeUint8(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PayloadConvert::addTime(time_t value) {
|
||||
uint32_t time = (uint32_t)value;
|
||||
writeUint32(time);
|
||||
}
|
||||
|
||||
void PayloadConvert::uintToBytes(uint64_t value, uint8_t byteSize) {
|
||||
for (uint8_t x = 0; x < byteSize; x++) {
|
||||
byte next = 0;
|
||||
if (sizeof(value) > x) {
|
||||
next = static_cast<byte>((value >> (x * 8)) & 0xFF);
|
||||
}
|
||||
buffer[cursor] = next;
|
||||
++cursor;
|
||||
}
|
||||
}
|
||||
|
||||
void PayloadConvert::writeUptime(uint64_t uptime) {
|
||||
writeUint64(uptime);
|
||||
}
|
||||
|
||||
void PayloadConvert::writeVersion(char *version) {
|
||||
memcpy(buffer + cursor, version, 10);
|
||||
cursor += 10;
|
||||
}
|
||||
|
||||
void PayloadConvert::writeLatLng(double latitude, double longitude) {
|
||||
// Tested to at least work with int32_t, which are processed correctly.
|
||||
writeUint32(latitude);
|
||||
writeUint32(longitude);
|
||||
}
|
||||
|
||||
void PayloadConvert::writeUint64(uint64_t i) { uintToBytes(i, 8); }
|
||||
|
||||
void PayloadConvert::writeUint32(uint32_t i) { uintToBytes(i, 4); }
|
||||
|
||||
void PayloadConvert::writeUint16(uint16_t i) { uintToBytes(i, 2); }
|
||||
|
||||
void PayloadConvert::writeUint8(uint8_t i) { uintToBytes(i, 1); }
|
||||
|
||||
void PayloadConvert::writeUFloat(float value) {
|
||||
writeUint16(value * 100);
|
||||
}
|
||||
|
||||
void PayloadConvert::writePressure(float value) {
|
||||
writeUint16(value * 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a 16bit two's complement with two decimals, so the range is
|
||||
* -327.68 to +327.67 degrees
|
||||
*/
|
||||
void PayloadConvert::writeFloat(float value) {
|
||||
int16_t t = (int16_t)(value * 100);
|
||||
if (value < 0) {
|
||||
t = ~-t;
|
||||
t = t + 1;
|
||||
}
|
||||
buffer[cursor++] = (byte)((t >> 8) & 0xFF);
|
||||
buffer[cursor++] = (byte)t & 0xFF;
|
||||
}
|
||||
|
||||
void PayloadConvert::writeBitmap(bool a, bool b, bool c, bool d, bool e, bool f,
|
||||
bool g, bool h) {
|
||||
uint8_t bitmap = 0;
|
||||
// LSB first
|
||||
bitmap |= (a & 1) << 7;
|
||||
bitmap |= (b & 1) << 6;
|
||||
bitmap |= (c & 1) << 5;
|
||||
bitmap |= (d & 1) << 4;
|
||||
bitmap |= (e & 1) << 3;
|
||||
bitmap |= (f & 1) << 2;
|
||||
bitmap |= (g & 1) << 1;
|
||||
bitmap |= (h & 1) << 0;
|
||||
writeUint8(bitmap);
|
||||
}
|
||||
|
||||
/* ---------------- Cayenne LPP 2.0 format ---------- */
|
||||
// see specs
|
||||
// http://community.mydevices.com/t/cayenne-lpp-2-0/7510 (LPP 2.0)
|
||||
// https://github.com/myDevicesIoT/cayenne-docs/blob/master/docs/LORA.md
|
||||
// (LPP 1.0) PAYLOAD_ENCODER == 3 -> Dynamic Sensor Payload, using channels ->
|
||||
// FPort 1 PAYLOAD_ENCODER == 4 -> Packed Sensor Payload, not using channels ->
|
||||
// FPort 2
|
||||
|
||||
#elif ((PAYLOAD_ENCODER == 3) || (PAYLOAD_ENCODER == 4))
|
||||
|
||||
void PayloadConvert::addByte(uint8_t value) {
|
||||
/*
|
||||
not implemented
|
||||
*/ }
|
||||
|
||||
void PayloadConvert::addCount(uint16_t value, uint8_t snifftype) {
|
||||
switch (snifftype) {
|
||||
case MAC_SNIFF_WIFI:
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_COUNT_WIFI_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] =
|
||||
LPP_LUMINOSITY; // workaround since cayenne has no data type meter
|
||||
buffer[cursor++] = highByte(value);
|
||||
buffer[cursor++] = lowByte(value);
|
||||
break;
|
||||
case MAC_SNIFF_BLE:
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_COUNT_BLE_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] =
|
||||
LPP_LUMINOSITY; // workaround since cayenne has no data type meter
|
||||
buffer[cursor++] = highByte(value);
|
||||
buffer[cursor++] = lowByte(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PayloadConvert::addAlarm(int8_t rssi, uint8_t msg) {
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_ALARM_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_PRESENCE;
|
||||
buffer[cursor++] = msg;
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_MSG_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_ANALOG_INPUT;
|
||||
buffer[cursor++] = rssi;
|
||||
}
|
||||
|
||||
void PayloadConvert::addVoltage(uint16_t value) {
|
||||
uint16_t volt = value / 10;
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_BATT_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_ANALOG_INPUT;
|
||||
buffer[cursor++] = highByte(volt);
|
||||
buffer[cursor++] = lowByte(volt);
|
||||
}
|
||||
|
||||
void PayloadConvert::addConfig(configData_t value) {
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_ADR_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_DIGITAL_INPUT;
|
||||
buffer[cursor++] = value.adrmode;
|
||||
}
|
||||
|
||||
void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius,
|
||||
uint32_t mem, uint8_t reset1, uint8_t reset2) {
|
||||
uint16_t temp = celsius * 10;
|
||||
uint16_t volt = voltage / 10;
|
||||
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_BATT_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_ANALOG_INPUT;
|
||||
buffer[cursor++] = highByte(volt);
|
||||
buffer[cursor++] = lowByte(volt);
|
||||
#endif // BAT_MEASURE_ADC
|
||||
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_TEMPERATURE_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_TEMPERATURE;
|
||||
buffer[cursor++] = highByte(temp);
|
||||
buffer[cursor++] = lowByte(temp);
|
||||
}
|
||||
|
||||
void PayloadConvert::addGPS(gpsStatus_t value) {
|
||||
#if(HAS_GPS)
|
||||
int32_t lat = value.latitude / 100;
|
||||
int32_t lon = value.longitude / 100;
|
||||
int32_t alt = value.altitude * 100;
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_GPS_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_GPS;
|
||||
buffer[cursor++] = (byte)((lat & 0xFF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((lat & 0x00FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((lat & 0x0000FF));
|
||||
buffer[cursor++] = (byte)((lon & 0xFF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((lon & 0x00FF00) >> 8);
|
||||
buffer[cursor++] = (byte)(lon & 0x0000FF);
|
||||
buffer[cursor++] = (byte)((alt & 0xFF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((alt & 0x00FF00) >> 8);
|
||||
buffer[cursor++] = (byte)(alt & 0x0000FF);
|
||||
#endif // HAS_GPS
|
||||
}
|
||||
|
||||
void PayloadConvert::addSensor(uint8_t buf[]) {
|
||||
#if(HAS_SENSORS)
|
||||
// to come
|
||||
/*
|
||||
uint8_t length = buf[0];
|
||||
memcpy(buffer, buf+1, length);
|
||||
cursor += length; // length of buffer
|
||||
*/
|
||||
#endif // HAS_SENSORS
|
||||
}
|
||||
|
||||
void PayloadConvert::addBME(bmeStatus_t value) {
|
||||
#if(HAS_BME)
|
||||
|
||||
// data value conversions to meet cayenne data type definition
|
||||
// 0.1°C per bit => -3276,7 .. +3276,7 °C
|
||||
int16_t temperature = (int16_t)(value.temperature * 10.0);
|
||||
// 0.1 hPa per bit => 0 .. 6553,6 hPa
|
||||
uint16_t pressure = (uint16_t)(value.pressure * 10);
|
||||
// 0.5% per bit => 0 .. 128 %C
|
||||
uint8_t humidity = (uint8_t)(value.humidity * 2.0);
|
||||
int16_t iaq = (int16_t)(value.iaq);
|
||||
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_TEMPERATURE_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_TEMPERATURE; // 2 bytes 0.1 °C Signed MSB
|
||||
buffer[cursor++] = highByte(temperature);
|
||||
buffer[cursor++] = lowByte(temperature);
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_BAROMETER_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_BAROMETER; // 2 bytes 0.1 hPa Unsigned MSB
|
||||
buffer[cursor++] = highByte(pressure);
|
||||
buffer[cursor++] = lowByte(pressure);
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_HUMIDITY_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_HUMIDITY; // 1 byte 0.5 % Unsigned
|
||||
buffer[cursor++] = humidity;
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_AIR_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_LUMINOSITY; // 2 bytes, 1.0 unsigned
|
||||
buffer[cursor++] = highByte(iaq);
|
||||
buffer[cursor++] = lowByte(iaq);
|
||||
#endif // HAS_BME
|
||||
}
|
||||
|
||||
void PayloadConvert::addButton(uint8_t value) {
|
||||
#ifdef HAS_BUTTON
|
||||
#if (PAYLOAD_ENCODER == 3)
|
||||
buffer[cursor++] = LPP_BUTTON_CHANNEL;
|
||||
#endif
|
||||
buffer[cursor++] = LPP_DIGITAL_INPUT;
|
||||
buffer[cursor++] = value;
|
||||
#endif // HAS_BUTTON
|
||||
}
|
||||
|
||||
void PayloadConvert::addTime(time_t value) {
|
||||
#if (PAYLOAD_ENCODER == 4)
|
||||
uint32_t t = (uint32_t)value;
|
||||
uint32_t tx_period = (uint32_t)SENDCYCLE * 2;
|
||||
buffer[cursor++] = 0x03; // set config mask to UTCTime + TXPeriod
|
||||
// UTCTime in seconds
|
||||
buffer[cursor++] = (byte)((t & 0xFF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((t & 0x00FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((t & 0x0000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((t & 0x000000FF));
|
||||
// TXPeriod in seconds
|
||||
buffer[cursor++] = (byte)((tx_period & 0xFF000000) >> 24);
|
||||
buffer[cursor++] = (byte)((tx_period & 0x00FF0000) >> 16);
|
||||
buffer[cursor++] = (byte)((tx_period & 0x0000FF00) >> 8);
|
||||
buffer[cursor++] = (byte)((tx_period & 0x000000FF));
|
||||
#endif
|
||||
}
|
||||
#endif // PAYLOAD_ENCODER
|
||||
|
||||
void PayloadConvert::addPM10( float value) {
|
||||
#if (HAS_SDS011)
|
||||
#if (PAYLOAD_ENCODER == 1) // plain
|
||||
|
@ -19,7 +19,7 @@ static void createFile(void);
|
||||
|
||||
File fileSDCard;
|
||||
|
||||
bool sdcardInit() {
|
||||
bool sdcard_init() {
|
||||
ESP_LOGD(TAG, "looking for SD-card...");
|
||||
useSDCard = SD.begin(SDCARD_CS, SDCARD_MOSI, SDCARD_MISO, SDCARD_SCLK);
|
||||
if (useSDCard)
|
||||
@ -43,7 +43,6 @@ void sdcardWriteData(uint16_t noWifi, uint16_t noBle) {
|
||||
sprintf(tempBuffer, "%d,%d", noWifi, noBle);
|
||||
fileSDCard.print( tempBuffer);
|
||||
#if (HAS_SDS011)
|
||||
ESP_LOGD(TAG, "fine-dust-values: %5.1f,%4.1f", pm10, pm25);
|
||||
sprintf(tempBuffer, ",%5.1f,%4.1f", pm10, pm25);
|
||||
fileSDCard.print( tempBuffer);
|
||||
#endif
|
||||
@ -84,4 +83,41 @@ void createFile(void) {
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // (HAS_SDCARD)
|
||||
|
||||
if (++counterWrites > 2) {
|
||||
// force writing to SD-card
|
||||
ESP_LOGD(TAG, "flushing data to card");
|
||||
fileSDCard.flush();
|
||||
counterWrites = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void createFile(void) {
|
||||
char bufferFilename[8 + 1 + 3 + 1];
|
||||
|
||||
useSDCard = false;
|
||||
|
||||
for (int i = 0; i < 100; i++) {
|
||||
sprintf(bufferFilename, SDCARD_FILE_NAME, i);
|
||||
ESP_LOGD(TAG, "SD: looking for file <%s>", bufferFilename);
|
||||
bool fileExists = SD.exists(bufferFilename);
|
||||
if (!fileExists) {
|
||||
ESP_LOGD(TAG, "SD: file does not exist: opening");
|
||||
fileSDCard = SD.open(bufferFilename, FILE_WRITE);
|
||||
if (fileSDCard) {
|
||||
ESP_LOGD(TAG, "SD: name opened: <%s>", bufferFilename);
|
||||
fileSDCard.print( SDCARD_FILE_HEADER );
|
||||
#if (HAS_SDS011)
|
||||
fileSDCard.print( SDCARD_FILE_HEADER_SDS011 );
|
||||
#endif
|
||||
fileSDCard.println();
|
||||
useSDCard = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // (HAS_SDCARD)
|
||||
|
@ -6,12 +6,16 @@ static const char TAG[] = __FILE__;
|
||||
#include <sds011read.h>
|
||||
|
||||
// UART(2) is unused in this project
|
||||
#if (HAS_IF482)
|
||||
#error cannot use IF482 together with SDS011 (both use UART#2)
|
||||
#endif
|
||||
static HardwareSerial sdsSerial(2); // so we use it here
|
||||
static SDS011 sdsSensor; // fine dust sensor
|
||||
|
||||
// the results of the sensor:
|
||||
float pm25;
|
||||
float pm10;
|
||||
boolean isSDS011Active;
|
||||
|
||||
// init
|
||||
bool sds011_init()
|
||||
@ -19,18 +23,40 @@ bool sds011_init()
|
||||
pm25 = pm10 = 0.0;
|
||||
sdsSerial.begin(9600, SERIAL_8N1, ESP_PIN_RX, ESP_PIN_TX);
|
||||
sdsSensor.begin (&sdsSerial);
|
||||
sdsSensor.contmode(0); // for safety: wakeup/sleep - if we want it we do it by ourselves
|
||||
sdsSensor.wakeup(); // always wake up
|
||||
sdsSensor.contmode(0); // for safety: no wakeup/sleep by the sensor
|
||||
sds011_sleep(); // we do it by ourselves
|
||||
return true;
|
||||
}
|
||||
// reading data:
|
||||
void sds011_loop()
|
||||
{
|
||||
pm25 = pm10 = 0.0;
|
||||
int sdsErrorCode = sdsSensor.read(&pm25, &pm10);
|
||||
if (!sdsErrorCode)
|
||||
{
|
||||
ESP_LOGD(TAG, "SDS011 error: %d", sdsErrorCode);
|
||||
if ( isSDS011Active ) {
|
||||
int sdsErrorCode = sdsSensor.read(&pm25, &pm10);
|
||||
if (sdsErrorCode) {
|
||||
pm25 = pm10 = 0.0;
|
||||
ESP_LOGI(TAG, "SDS011 error: %d", sdsErrorCode);
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(TAG, "fine-dust-values: %5.1f,%4.1f", pm10, pm25);
|
||||
}
|
||||
sds011_sleep();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// putting the SDS-sensor to sleep
|
||||
void sds011_sleep(void)
|
||||
{
|
||||
sdsSensor.sleep();
|
||||
isSDS011Active = false;
|
||||
}
|
||||
|
||||
// start the SDS-sensor
|
||||
// needs 30 seconds for warming up
|
||||
void sds011_wakeup()
|
||||
{
|
||||
if ( !isSDS011Active ) {
|
||||
sdsSensor.wakeup();
|
||||
isSDS011Active = true;
|
||||
}
|
||||
}
|
||||
|
147
src/senddata.cpp
147
src/senddata.cpp
@ -9,7 +9,10 @@ extern float pm25;
|
||||
#endif
|
||||
|
||||
void sendcycle() {
|
||||
static int counter = 0;
|
||||
xTaskNotifyFromISR(irqHandlerTask, SENDCYCLE_IRQ, eSetBits, NULL);
|
||||
ESP_LOGI( TAG, "in sendcycle(): %d", counter++);
|
||||
|
||||
}
|
||||
|
||||
// put data to send in RTos Queues used for transmit over channels Lora and SPI
|
||||
@ -18,9 +21,9 @@ void SendPayload(uint8_t port, sendprio_t prio) {
|
||||
MessageBuffer_t
|
||||
SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[]
|
||||
|
||||
#if (HAS_SDS011)
|
||||
sds011_loop();
|
||||
#endif
|
||||
//#if (HAS_SDS011)
|
||||
// sds011_loop();
|
||||
//#endif
|
||||
|
||||
SendBuffer.MessageSize = payload.getSize();
|
||||
SendBuffer.MessagePrio = prio;
|
||||
@ -178,6 +181,144 @@ void sendData() {
|
||||
|
||||
} // sendData()
|
||||
|
||||
void flushQueues() {
|
||||
#if (HAS_LORA)
|
||||
lora_queuereset();
|
||||
#endif
|
||||
#ifdef HAS_SPI
|
||||
spi_queuereset();
|
||||
#endif
|
||||
}
|
||||
SendBuffer.MessagePort = port;
|
||||
}
|
||||
memcpy(SendBuffer.Message, payload.getBuffer(), SendBuffer.MessageSize);
|
||||
|
||||
// enqueue message in device's send queues
|
||||
#if (HAS_LORA)
|
||||
lora_enqueuedata(&SendBuffer);
|
||||
#endif
|
||||
#ifdef HAS_SPI
|
||||
spi_enqueuedata(&SendBuffer);
|
||||
#endif
|
||||
|
||||
// write data to sdcard, if present
|
||||
#ifdef HAS_SDCARD
|
||||
sdcardWriteData(macs_wifi, macs_ble);
|
||||
#endif
|
||||
|
||||
} // SendPayload
|
||||
|
||||
// interrupt triggered function to prepare payload to send
|
||||
void sendData() {
|
||||
|
||||
uint8_t bitmask = cfg.payloadmask;
|
||||
uint8_t mask = 1;
|
||||
#if (HAS_GPS)
|
||||
gpsStatus_t gps_status;
|
||||
#endif
|
||||
|
||||
while (bitmask) {
|
||||
switch (bitmask & mask) {
|
||||
|
||||
#if ((WIFICOUNTER) || (BLECOUNTER))
|
||||
case COUNT_DATA:
|
||||
payload.reset();
|
||||
#if !(PAYLOAD_OPENSENSEBOX)
|
||||
if (cfg.wifiscan)
|
||||
payload.addCount(macs_wifi, MAC_SNIFF_WIFI);
|
||||
if (cfg.blescan)
|
||||
payload.addCount(macs_ble, MAC_SNIFF_BLE);
|
||||
#endif
|
||||
#if (HAS_GPS)
|
||||
if (GPSPORT == COUNTERPORT) {
|
||||
// send GPS position only if we have a fix
|
||||
if (gps_hasfix()) {
|
||||
gps_storelocation(&gps_status);
|
||||
payload.addGPS(gps_status);
|
||||
} else
|
||||
ESP_LOGD(TAG, "No valid GPS position");
|
||||
}
|
||||
#endif
|
||||
#if (PAYLOAD_OPENSENSEBOX)
|
||||
if (cfg.wifiscan)
|
||||
payload.addCount(macs_wifi, MAC_SNIFF_WIFI);
|
||||
if (cfg.blescan)
|
||||
payload.addCount(macs_ble, MAC_SNIFF_BLE);
|
||||
#endif
|
||||
#if (HAS_SDS011)
|
||||
payload.addPM10(pm10);
|
||||
payload.addPM25(pm25);
|
||||
#endif
|
||||
SendPayload(COUNTERPORT, prio_normal);
|
||||
// clear counter if not in cumulative counter mode
|
||||
if (cfg.countermode != 1) {
|
||||
reset_counters(); // clear macs container and reset all counters
|
||||
get_salt(); // get new salt for salting hashes
|
||||
ESP_LOGI(TAG, "Counter cleared");
|
||||
}
|
||||
#ifdef HAS_DISPLAY
|
||||
else
|
||||
oledPlotCurve(macs.size(), true);
|
||||
#endif
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (HAS_BME)
|
||||
case MEMS_DATA:
|
||||
payload.reset();
|
||||
payload.addBME(bme_status);
|
||||
SendPayload(BMEPORT, prio_normal);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (HAS_GPS)
|
||||
case GPS_DATA:
|
||||
if (GPSPORT != COUNTERPORT) {
|
||||
// send GPS position only if we have a fix
|
||||
if (gps_hasfix()) {
|
||||
gps_storelocation(&gps_status);
|
||||
payload.reset();
|
||||
payload.addGPS(gps_status);
|
||||
SendPayload(GPSPORT, prio_high);
|
||||
} else
|
||||
ESP_LOGD(TAG, "No valid GPS position");
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (HAS_SENSORS)
|
||||
case SENSOR1_DATA:
|
||||
payload.reset();
|
||||
payload.addSensor(sensor_read(1));
|
||||
SendPayload(SENSOR1PORT, prio_normal);
|
||||
break;
|
||||
case SENSOR2_DATA:
|
||||
payload.reset();
|
||||
payload.addSensor(sensor_read(2));
|
||||
SendPayload(SENSOR2PORT, prio_normal);
|
||||
break;
|
||||
case SENSOR3_DATA:
|
||||
payload.reset();
|
||||
payload.addSensor(sensor_read(3));
|
||||
SendPayload(SENSOR3PORT, prio_normal);
|
||||
break;
|
||||
#endif
|
||||
|
||||
#if (defined BAT_MEASURE_ADC || defined HAS_PMU)
|
||||
case BATT_DATA:
|
||||
payload.reset();
|
||||
payload.addVoltage(read_voltage());
|
||||
SendPayload(BATTPORT, prio_normal);
|
||||
break;
|
||||
#endif
|
||||
|
||||
} // switch
|
||||
bitmask &= ~mask;
|
||||
mask <<= 1;
|
||||
} // while (bitmask)
|
||||
|
||||
} // sendData()
|
||||
|
||||
void flushQueues() {
|
||||
#if (HAS_LORA)
|
||||
lora_queuereset();
|
||||
|
@ -16,6 +16,9 @@ static const char TAG[] = __FILE__;
|
||||
const char timeSetSymbols[] = {'G', 'R', 'L', '?'};
|
||||
|
||||
#ifdef HAS_IF482
|
||||
#if (HAS_SDS011)
|
||||
#error cannot use IF482 together with SDS011 (both use UART#2)
|
||||
#endif
|
||||
HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
|
||||
#endif
|
||||
|
||||
@ -317,4 +320,274 @@ void clock_loop(void *taskparameter) { // ClockTask
|
||||
} // for
|
||||
} // clock_loop()
|
||||
|
||||
#endif // HAS_IF482 || defined HAS_DCF77
|
||||
if (t) {
|
||||
timeSource = _rtc;
|
||||
goto finish;
|
||||
}
|
||||
#endif
|
||||
|
||||
goto finish;
|
||||
|
||||
finish:
|
||||
|
||||
setMyTime((uint32_t)t, t_msec, timeSource); // set time
|
||||
|
||||
} // calibrateTime()
|
||||
|
||||
// adjust system time, calibrate RTC and RTC_INT pps
|
||||
void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec,
|
||||
timesource_t mytimesource) {
|
||||
|
||||
// called with invalid timesource?
|
||||
if (mytimesource == _unsynced)
|
||||
return;
|
||||
|
||||
// increment t_sec only if t_msec > 1000
|
||||
time_t time_to_set = (time_t)(t_sec + t_msec / 1000);
|
||||
|
||||
// do we have a valid time?
|
||||
if (timeIsValid(time_to_set)) {
|
||||
|
||||
// if we have msec fraction, then wait until top of second with
|
||||
// millisecond precision
|
||||
if (t_msec % 1000) {
|
||||
time_to_set++;
|
||||
vTaskDelay(pdMS_TO_TICKS(1000 - t_msec % 1000));
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] UTC epoch time: %d.%03d sec", millis() / 1000.0,
|
||||
time_to_set, t_msec % 1000);
|
||||
|
||||
// if we have got an external timesource, set RTC time and shift RTC_INT pulse
|
||||
// to top of second
|
||||
#ifdef HAS_RTC
|
||||
if ((mytimesource == _gps) || (mytimesource == _lora))
|
||||
set_rtctime(time_to_set);
|
||||
#endif
|
||||
|
||||
// if we have a software pps timer, shift it to top of second
|
||||
#if (!defined GPS_INT && !defined RTC_INT)
|
||||
timerWrite(ppsIRQ, 0); // reset pps timer
|
||||
CLOCKIRQ(); // fire clock pps, this advances time 1 sec
|
||||
#endif
|
||||
|
||||
setTime(time_to_set); // set the time on top of second
|
||||
|
||||
timeSource = mytimesource; // set global variable
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync finished, time was set | source: %c",
|
||||
millis() / 1000.0, timeSetSymbols[timeSource]);
|
||||
} else {
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
|
||||
ESP_LOGI(TAG, "[%0.3f] Timesync failed, invalid time fetched | source: %c",
|
||||
millis() / 1000.0, timeSetSymbols[timeSource]);
|
||||
}
|
||||
}
|
||||
|
||||
// helper function to setup a pulse per second for time synchronisation
|
||||
uint8_t timepulse_init() {
|
||||
|
||||
// use time pulse from GPS as time base with fixed 1Hz frequency
|
||||
#ifdef GPS_INT
|
||||
|
||||
// setup external interupt pin for rising edge GPS INT
|
||||
pinMode(GPS_INT, INPUT_PULLDOWN);
|
||||
// setup external rtc 1Hz clock as pulse per second clock
|
||||
ESP_LOGI(TAG, "Timepulse: external (GPS)");
|
||||
return 1; // success
|
||||
|
||||
// use pulse from on board RTC chip as time base with fixed frequency
|
||||
#elif defined RTC_INT
|
||||
|
||||
// setup external interupt pin for falling edge RTC INT
|
||||
pinMode(RTC_INT, INPUT_PULLUP);
|
||||
|
||||
// setup external rtc 1Hz clock as pulse per second clock
|
||||
if (I2C_MUTEX_LOCK()) {
|
||||
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
|
||||
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
|
||||
I2C_MUTEX_UNLOCK();
|
||||
ESP_LOGI(TAG, "Timepulse: external (RTC)");
|
||||
return 1; // success
|
||||
} else {
|
||||
ESP_LOGE(TAG, "RTC initialization error, I2C bus busy");
|
||||
return 0; // failure
|
||||
}
|
||||
return 1; // success
|
||||
|
||||
#else
|
||||
// use ESP32 hardware timer as time base with adjustable frequency
|
||||
ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
|
||||
timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms
|
||||
ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)");
|
||||
return 1; // success
|
||||
|
||||
#endif
|
||||
} // timepulse_init
|
||||
|
||||
void timepulse_start(void) {
|
||||
|
||||
#ifdef GPS_INT // start external clock gps pps line
|
||||
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
|
||||
#elif defined RTC_INT // start external clock rtc
|
||||
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
|
||||
#else // start internal clock esp32 hardware timer
|
||||
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
|
||||
timerAlarmEnable(ppsIRQ);
|
||||
#endif
|
||||
|
||||
// start cyclic time sync
|
||||
timeSync(); // init systime by RTC or GPS or LORA
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
||||
}
|
||||
|
||||
// interrupt service routine triggered by either pps or esp32 hardware timer
|
||||
void IRAM_ATTR CLOCKIRQ(void) {
|
||||
|
||||
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||
|
||||
SyncToPPS(); // advance systime, see microTime.h
|
||||
|
||||
// advance wall clock, if we have
|
||||
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
||||
&xHigherPriorityTaskWoken);
|
||||
#endif
|
||||
|
||||
// flip time pulse ticker, if needed
|
||||
#ifdef HAS_DISPLAY
|
||||
#if (defined GPS_INT || defined RTC_INT)
|
||||
TimePulseTick = !TimePulseTick; // flip pulse ticker
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// yield only if we should
|
||||
if (xHigherPriorityTaskWoken)
|
||||
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
|
||||
return (t >= compiledUTC() ? t : 0);
|
||||
}
|
||||
|
||||
// helper function to convert compile time to UTC time
|
||||
time_t compiledUTC(void) {
|
||||
static time_t t = myTZ.toUTC(RtcDateTime(__DATE__, __TIME__).Epoch32Time());
|
||||
return t;
|
||||
}
|
||||
|
||||
// helper function to calculate serial transmit time
|
||||
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
|
||||
int8_t rxPin, int8_t txPins) {
|
||||
|
||||
uint32_t databits = ((config & 0x0c) >> 2) + 5;
|
||||
uint32_t stopbits = ((config & 0x20) >> 5) + 1;
|
||||
uint32_t txTime = (databits + stopbits + 1) * framesize * 1000.0 / baud;
|
||||
// +1 for the startbit
|
||||
|
||||
return round(txTime);
|
||||
}
|
||||
|
||||
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||
|
||||
#if (defined HAS_DCF77 && defined HAS_IF482)
|
||||
#error You must define at most one of IF482 or DCF77!
|
||||
#endif
|
||||
|
||||
void clock_init(void) {
|
||||
|
||||
// setup clock output interface
|
||||
#ifdef HAS_IF482
|
||||
IF482.begin(HAS_IF482);
|
||||
#elif defined HAS_DCF77
|
||||
pinMode(HAS_DCF77, OUTPUT);
|
||||
#endif
|
||||
|
||||
userUTCTime = now();
|
||||
|
||||
xTaskCreatePinnedToCore(clock_loop, // task function
|
||||
"clockloop", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)&userUTCTime, // start time as task parameter
|
||||
4, // priority of the task
|
||||
&ClockTask, // task handle
|
||||
1); // CPU core
|
||||
|
||||
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
|
||||
// due to concurrent access to i2c bus when reading/writing from/to rtc chip!
|
||||
|
||||
#define nextmin(t) (t + DCF77_FRAME_SIZE + 1) // next minute
|
||||
|
||||
#ifdef HAS_TWO_LED
|
||||
static bool led1_state = false;
|
||||
#endif
|
||||
uint32_t printtime;
|
||||
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
|
||||
|
||||
#ifdef HAS_DCF77
|
||||
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/telegram after pps arrived
|
||||
for (;;) {
|
||||
|
||||
// wait for timepulse and store UTC time in seconds got
|
||||
xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY);
|
||||
t = time_t(printtime);
|
||||
|
||||
// no confident or no recent time -> suppress clock output
|
||||
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
|
||||
(t == last_printtime))
|
||||
continue;
|
||||
|
||||
#if defined HAS_IF482
|
||||
|
||||
// wait until moment to fire. Normally we won't get notified during this
|
||||
// timespan, except when next pps pulse arrives while waiting, because pps
|
||||
// was adjusted by recent time sync
|
||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &printtime, txDelay) == pdTRUE)
|
||||
t = time_t(printtime); // new adjusted UTC time seconds
|
||||
|
||||
// send IF482 telegram
|
||||
IF482.print(IF482_Frame(t + 1)); // note: telegram is for *next* second
|
||||
|
||||
#elif defined HAS_DCF77
|
||||
|
||||
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
||||
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
|
||||
|
||||
if (minute(nextmin(t)) == // do we still have a recent frame?
|
||||
DCFpulse[DCF77_FRAME_SIZE]) // (timepulses could be missed!)
|
||||
DCF77_Pulse(t, DCFpulse); // then output current second's pulse
|
||||
|
||||
// else we have no recent frame, thus suppressing clock output
|
||||
|
||||
#endif
|
||||
|
||||
// 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
|
||||
|
||||
last_printtime = t;
|
||||
|
||||
} // for
|
||||
} // clock_loop()
|
||||
|
||||
#endif // HAS_IF482 || defined HAS_DCF77
|
||||
|
Loading…
Reference in New Issue
Block a user