commit
0580842a5f
@ -18,6 +18,15 @@
|
|||||||
#include "display.h"
|
#include "display.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if (HAS_SDS011)
|
||||||
|
#include "sds011read.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if (HAS_SDCARD)
|
||||||
|
#include "sdcard.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
extern Ticker housekeeper;
|
extern Ticker housekeeper;
|
||||||
|
|
||||||
void housekeeping(void);
|
void housekeeping(void);
|
||||||
|
@ -61,6 +61,62 @@ public:
|
|||||||
void addTime(time_t value);
|
void addTime(time_t value);
|
||||||
void addPM10(float value);
|
void addPM10(float value);
|
||||||
void addPM25(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);
|
void addChars( char* string, int len);
|
||||||
|
|
||||||
#if (PAYLOAD_ENCODER == 1) // format plain
|
#if (PAYLOAD_ENCODER == 1) // format plain
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
#define SDCARD_FILE_NAME "paxcount.%02d"
|
#define SDCARD_FILE_NAME "paxcount.%02d"
|
||||||
#define SDCARD_FILE_HEADER "date, time, wifi, bluet"
|
#define SDCARD_FILE_HEADER "date, time, wifi, bluet"
|
||||||
|
|
||||||
bool sdcardInit( void );
|
bool sdcard_init( void );
|
||||||
void sdcardWriteData( uint16_t, uint16_t);
|
void sdcardWriteData( uint16_t, uint16_t);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -11,5 +11,7 @@
|
|||||||
|
|
||||||
bool sds011_init();
|
bool sds011_init();
|
||||||
void sds011_loop();
|
void sds011_loop();
|
||||||
|
void sds011_sleep(void);
|
||||||
|
void sds011_wakeup(void);
|
||||||
|
|
||||||
#endif // _SDS011READ_H
|
#endif // _SDS011READ_H
|
||||||
|
116
src/cyclic.cpp
116
src/cyclic.cpp
@ -9,8 +9,14 @@ static const char TAG[] = __FILE__;
|
|||||||
|
|
||||||
Ticker housekeeper;
|
Ticker housekeeper;
|
||||||
|
|
||||||
|
#if (HAS_SDS011)
|
||||||
|
extern boolean isSDS011Active;
|
||||||
|
#endif
|
||||||
|
|
||||||
void housekeeping() {
|
void housekeeping() {
|
||||||
|
static int counter = 0;
|
||||||
xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL);
|
xTaskNotifyFromISR(irqHandlerTask, CYCLIC_IRQ, eSetBits, NULL);
|
||||||
|
ESP_LOGI( TAG, "in Housekeeping(): %d", counter++);
|
||||||
}
|
}
|
||||||
|
|
||||||
// do all housekeeping
|
// do all housekeeping
|
||||||
@ -18,7 +24,6 @@ void doHousekeeping() {
|
|||||||
|
|
||||||
// update uptime counter
|
// update uptime counter
|
||||||
uptime();
|
uptime();
|
||||||
|
|
||||||
// check if update mode trigger switch was set
|
// check if update mode trigger switch was set
|
||||||
if (RTC_runmode == RUNMODE_UPDATE) {
|
if (RTC_runmode == RUNMODE_UPDATE) {
|
||||||
// check battery status if we can before doing ota
|
// check battery status if we can before doing ota
|
||||||
@ -112,6 +117,113 @@ void doHousekeeping() {
|
|||||||
}
|
}
|
||||||
#endif
|
#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()
|
} // doHousekeeping()
|
||||||
|
|
||||||
// uptime counter 64bit to prevent millis() rollover after 49 days
|
// uptime counter 64bit to prevent millis() rollover after 49 days
|
||||||
@ -143,4 +255,4 @@ void reset_counters() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
437
src/main.cpp
437
src/main.cpp
@ -323,6 +323,443 @@ void setup() {
|
|||||||
assert(spi_init() == ESP_OK);
|
assert(spi_init() == ESP_OK);
|
||||||
#endif
|
#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
|
#ifdef HAS_SDCARD
|
||||||
if (sdcardInit())
|
if (sdcardInit())
|
||||||
strcat_P(features, " SD");
|
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
|
#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) {
|
void PayloadConvert::addPM10( float value) {
|
||||||
#if (HAS_SDS011)
|
#if (HAS_SDS011)
|
||||||
#if (PAYLOAD_ENCODER == 1) // plain
|
#if (PAYLOAD_ENCODER == 1) // plain
|
||||||
|
@ -19,7 +19,7 @@ static void createFile(void);
|
|||||||
|
|
||||||
File fileSDCard;
|
File fileSDCard;
|
||||||
|
|
||||||
bool sdcardInit() {
|
bool sdcard_init() {
|
||||||
ESP_LOGD(TAG, "looking for SD-card...");
|
ESP_LOGD(TAG, "looking for SD-card...");
|
||||||
useSDCard = SD.begin(SDCARD_CS, SDCARD_MOSI, SDCARD_MISO, SDCARD_SCLK);
|
useSDCard = SD.begin(SDCARD_CS, SDCARD_MOSI, SDCARD_MISO, SDCARD_SCLK);
|
||||||
if (useSDCard)
|
if (useSDCard)
|
||||||
@ -43,7 +43,6 @@ void sdcardWriteData(uint16_t noWifi, uint16_t noBle) {
|
|||||||
sprintf(tempBuffer, "%d,%d", noWifi, noBle);
|
sprintf(tempBuffer, "%d,%d", noWifi, noBle);
|
||||||
fileSDCard.print( tempBuffer);
|
fileSDCard.print( tempBuffer);
|
||||||
#if (HAS_SDS011)
|
#if (HAS_SDS011)
|
||||||
ESP_LOGD(TAG, "fine-dust-values: %5.1f,%4.1f", pm10, pm25);
|
|
||||||
sprintf(tempBuffer, ",%5.1f,%4.1f", pm10, pm25);
|
sprintf(tempBuffer, ",%5.1f,%4.1f", pm10, pm25);
|
||||||
fileSDCard.print( tempBuffer);
|
fileSDCard.print( tempBuffer);
|
||||||
#endif
|
#endif
|
||||||
@ -84,4 +83,41 @@ void createFile(void) {
|
|||||||
return;
|
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)
|
#endif // (HAS_SDCARD)
|
||||||
|
@ -6,12 +6,16 @@ static const char TAG[] = __FILE__;
|
|||||||
#include <sds011read.h>
|
#include <sds011read.h>
|
||||||
|
|
||||||
// UART(2) is unused in this project
|
// 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 HardwareSerial sdsSerial(2); // so we use it here
|
||||||
static SDS011 sdsSensor; // fine dust sensor
|
static SDS011 sdsSensor; // fine dust sensor
|
||||||
|
|
||||||
// the results of the sensor:
|
// the results of the sensor:
|
||||||
float pm25;
|
float pm25;
|
||||||
float pm10;
|
float pm10;
|
||||||
|
boolean isSDS011Active;
|
||||||
|
|
||||||
// init
|
// init
|
||||||
bool sds011_init()
|
bool sds011_init()
|
||||||
@ -19,18 +23,40 @@ bool sds011_init()
|
|||||||
pm25 = pm10 = 0.0;
|
pm25 = pm10 = 0.0;
|
||||||
sdsSerial.begin(9600, SERIAL_8N1, ESP_PIN_RX, ESP_PIN_TX);
|
sdsSerial.begin(9600, SERIAL_8N1, ESP_PIN_RX, ESP_PIN_TX);
|
||||||
sdsSensor.begin (&sdsSerial);
|
sdsSensor.begin (&sdsSerial);
|
||||||
sdsSensor.contmode(0); // for safety: wakeup/sleep - if we want it we do it by ourselves
|
sdsSensor.contmode(0); // for safety: no wakeup/sleep by the sensor
|
||||||
sdsSensor.wakeup(); // always wake up
|
sds011_sleep(); // we do it by ourselves
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// reading data:
|
// reading data:
|
||||||
void sds011_loop()
|
void sds011_loop()
|
||||||
{
|
{
|
||||||
pm25 = pm10 = 0.0;
|
if ( isSDS011Active ) {
|
||||||
int sdsErrorCode = sdsSensor.read(&pm25, &pm10);
|
int sdsErrorCode = sdsSensor.read(&pm25, &pm10);
|
||||||
if (!sdsErrorCode)
|
if (sdsErrorCode) {
|
||||||
{
|
pm25 = pm10 = 0.0;
|
||||||
ESP_LOGD(TAG, "SDS011 error: %d", sdsErrorCode);
|
ESP_LOGI(TAG, "SDS011 error: %d", sdsErrorCode);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGI(TAG, "fine-dust-values: %5.1f,%4.1f", pm10, pm25);
|
||||||
|
}
|
||||||
|
sds011_sleep();
|
||||||
|
}
|
||||||
return;
|
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
|
#endif
|
||||||
|
|
||||||
void sendcycle() {
|
void sendcycle() {
|
||||||
|
static int counter = 0;
|
||||||
xTaskNotifyFromISR(irqHandlerTask, SENDCYCLE_IRQ, eSetBits, NULL);
|
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
|
// 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
|
MessageBuffer_t
|
||||||
SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[]
|
SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[]
|
||||||
|
|
||||||
#if (HAS_SDS011)
|
//#if (HAS_SDS011)
|
||||||
sds011_loop();
|
// sds011_loop();
|
||||||
#endif
|
//#endif
|
||||||
|
|
||||||
SendBuffer.MessageSize = payload.getSize();
|
SendBuffer.MessageSize = payload.getSize();
|
||||||
SendBuffer.MessagePrio = prio;
|
SendBuffer.MessagePrio = prio;
|
||||||
@ -178,6 +181,144 @@ void sendData() {
|
|||||||
|
|
||||||
} // 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() {
|
void flushQueues() {
|
||||||
#if (HAS_LORA)
|
#if (HAS_LORA)
|
||||||
lora_queuereset();
|
lora_queuereset();
|
||||||
|
@ -16,6 +16,9 @@ static const char TAG[] = __FILE__;
|
|||||||
const char timeSetSymbols[] = {'G', 'R', 'L', '?'};
|
const char timeSetSymbols[] = {'G', 'R', 'L', '?'};
|
||||||
|
|
||||||
#ifdef HAS_IF482
|
#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)
|
HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -317,4 +320,274 @@ void clock_loop(void *taskparameter) { // ClockTask
|
|||||||
} // for
|
} // for
|
||||||
} // clock_loop()
|
} // 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
|
#endif // HAS_IF482 || defined HAS_DCF77
|
||||||
|
Loading…
Reference in New Issue
Block a user