Merge pull request #541 from AugustQu/SDS011

Sds011
This commit is contained in:
Verkehrsrot 2020-02-03 16:52:11 +01:00 committed by GitHub
commit 0580842a5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1577 additions and 17 deletions

View File

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

View File

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

View File

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

View File

@ -11,5 +11,7 @@
bool sds011_init();
void sds011_loop();
void sds011_sleep(void);
void sds011_wakeup(void);
#endif // _SDS011READ_H

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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