diff --git a/docs/configuration/index.md b/docs/configuration/index.md index 42cce627..0ed00637 100644 --- a/docs/configuration/index.md +++ b/docs/configuration/index.md @@ -90,7 +90,7 @@ Supported external time sources are GPS, LORAWAN network time and LORAWAN applic ## Syncing multiple paxcounters -A fleet of paxcounters can be synchronized to keep all devices wake up and start scanning at the same time. Synchronization is based on top-of-hour as common time point of reference. This feature requires time-of-day to be present on each device. Thus, `TIME_SYNC_INTERVAL` option, as explained above, must be enabled. Wake up syncing is enabled by setting `SYNCWAKEUP` in `paxcounter.conf` to a value X, greater than zero, and smaller than `SLEEPCYCLE`. This defines a time window, centered at top-of-hour, sized +/- X seconds. If a device, returning from sleep, would wakeup within this time window, it's wakeup will be adjusted to top-of-hour. +A fleet of paxcounters can be synchronized to keep all devices wake up and start scanning at the same time. Synchronization is based on top-of-hour as common time point of reference. This feature requires time-of-day to be present on each device. Thus, `TIME_SYNC_INTERVAL` option, as explained above, must be enabled. Wake up syncing is enabled by setting `SYNCWAKEUP` in `paxcounter.conf` to a value X, in seconds, greater than zero, and smaller than `SLEEPCYCLE` (in seconds/10). This defines a time window, centered at top-of-hour, sized +/- X seconds. If a device, returning from sleep, would wakeup within this time window, it's wakeup will be adjusted to top-of-hour. ## Wall clock controller diff --git a/docs/remotecontrol.md b/docs/remotecontrol.md index b1faafc3..d573ca12 100644 --- a/docs/remotecontrol.md +++ b/docs/remotecontrol.md @@ -91,6 +91,11 @@ Send for example `83` `86` as Downlink on Port 2 to get battery status and time/ 0 ... 255 duration for scanning a bluetooth advertising channel in seconds/100 e.g. 8 -> each channel is scanned for 80 milliseconds [default] +#### 0x0D set wakeup sync window + + bytes 1..2 = wakeup sync window size in seconds (MSB), 0..255 (0 = no wakuep sync) + e.g. {0x02, 0x58} -> device adjusts it's wakeup time when it is +/- 5 minutes from top-of-hour [default = 0] + #### 0x0E set Bluetooth scanner 0 = disabled diff --git a/include/globals.h b/include/globals.h index 35adfb7e..0b042d50 100644 --- a/include/globals.h +++ b/include/globals.h @@ -64,6 +64,7 @@ typedef struct __attribute__((packed)) { int16_t rssilimit; // threshold for rssilimiter, negative value! uint8_t sendcycle; // payload send cycle [seconds/2] uint16_t sleepcycle; // sleep cycle [seconds/10] + uint16_t wakesync; // time window [seconds] to sync wakeup on top-of-hour uint8_t wifichancycle; // wifi channel switch cycle [seconds/100] uint8_t blescantime; // BLE scan cycle duration [seconds] uint8_t blescan; // 0=disabled, 1=enabled @@ -109,6 +110,6 @@ typedef struct { float pm25; } sdsStatus_t; -extern char clientId[20]; // unique clientID +extern char clientId[20]; // unique clientID #endif \ No newline at end of file diff --git a/platformio_orig.ini b/platformio_orig.ini index bea258d0..4de41473 100644 --- a/platformio_orig.ini +++ b/platformio_orig.ini @@ -46,7 +46,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 3.4.7 +release_version = 3.5.0 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 @@ -73,7 +73,8 @@ lib_deps_sensors = adafruit/Adafruit Unified Sensor @ ^1.1.7 adafruit/Adafruit BME280 Library @ ^2.2.2 adafruit/Adafruit BMP085 Library @ ^1.2.2 - boschsensortec/BSEC Software Library @ 1.6.1480 + ;boschsensortec/BSEC Software Library @ 1.8.1492 + https://github.com/boschsensortec/BSEC-Arduino-library lewapek/Nova Fitness Sds dust sensors library @ ^1.5.1 lib_deps_basic = greyrook/libpax @ ^1.1.0 diff --git a/platformio_orig_s3.ini b/platformio_orig_s3.ini index 913f9c4c..8b841d62 100644 --- a/platformio_orig_s3.ini +++ b/platformio_orig_s3.ini @@ -10,7 +10,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 3.4.7 +release_version = 3.5.0 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 diff --git a/src/bmesensor.cpp b/src/bmesensor.cpp index 50e55821..1a1ae384 100644 --- a/src/bmesensor.cpp +++ b/src/bmesensor.cpp @@ -82,20 +82,20 @@ int bme_init(void) { int checkIaqSensorStatus(void) { int rslt = 1; // true = 1 = no error, false = 0 = error - if (iaqSensor.status != BSEC_OK) { + if (iaqSensor.bsecStatus != BSEC_OK) { rslt = 0; - if (iaqSensor.status < BSEC_OK) - ESP_LOGE(TAG, "BSEC error %d", iaqSensor.status); + if (iaqSensor.bsecStatus < BSEC_OK) + ESP_LOGE(TAG, "BSEC error %d", iaqSensor.bsecStatus); else - ESP_LOGW(TAG, "BSEC warning %d", iaqSensor.status); + ESP_LOGW(TAG, "BSEC warning %d", iaqSensor.bsecStatus); } - if (iaqSensor.bme680Status != BME680_OK) { + if (iaqSensor.bme68xStatus != BME68X_OK) { rslt = 0; - if (iaqSensor.bme680Status < BME680_OK) - ESP_LOGE(TAG, "BME680 error %d", iaqSensor.bme680Status); + if (iaqSensor.bme68xStatus < BME68X_OK) + ESP_LOGE(TAG, "BME680 error %d", iaqSensor.bme68xStatus); else - ESP_LOGW(TAG, "BME680 warning %d", iaqSensor.bme680Status); + ESP_LOGW(TAG, "BME680 warning %d", iaqSensor.bme68xStatus); } return rslt; diff --git a/src/configmanager.cpp b/src/configmanager.cpp index 7d6181bf..3360a4ef 100644 --- a/src/configmanager.cpp +++ b/src/configmanager.cpp @@ -3,7 +3,6 @@ #include "globals.h" #include "configmanager.h" - // namespace for device runtime preferences #define DEVCONFIG "paxcntcfg" @@ -38,6 +37,7 @@ static void defaultConfig(configData_t *myconfig) { myconfig->rssilimit = RSSILIMIT; // threshold for rssilimiter, negative value! myconfig->sendcycle = SENDCYCLE; // payload send cycle [seconds/2] myconfig->sleepcycle = SLEEPCYCLE; // sleep cycle [seconds/10] + myconfig->wakesync = SYNCWAKEUP; // wakeup sync window [seconds] myconfig->wifichancycle = WIFI_CHANNEL_SWITCH_INTERVAL; // wifi channel switch cycle [seconds/100] myconfig->blescantime = diff --git a/src/hal/generic.h b/src/hal/generic.h index 42849c45..cd339f48 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -37,7 +37,7 @@ // BME680 sensor on I2C bus #define HAS_BME 1 // Enable BME sensors in general #define HAS_BME680 GPIO_NUM_21, GPIO_NUM_22 // SDA, SCL -#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND +#define BME680_ADDR BME68X_I2C_ADDR_LOW // connect SDIO of BME680 to GND // BME280 sensor on I2C bus //#define HAS_BME 1 // Enable BME sensors in general diff --git a/src/hal/octopus32.h b/src/hal/octopus32.h index 6b5b66c4..35361030 100644 --- a/src/hal/octopus32.h +++ b/src/hal/octopus32.h @@ -18,7 +18,7 @@ // Octopus32 has a pre-populated BME680 on i2c addr 0x76 #define HAS_BME 1 // Enable BME sensors in general #define HAS_BME680 GPIO_NUM_23, GPIO_NUM_22 // SDA, SCL -#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // connect SDIO of BME680 to GND +#define BME680_ADDR BME68X_I2C_ADDR_LOW // connect SDIO of BME680 to GND #define HAS_LED 13 // ESP32 GPIO12 (pin22) On Board LED //#define LED_ACTIVE_LOW 1 // Onboard LED is active when pin is LOW diff --git a/src/hal/ttgobeam.h b/src/hal/ttgobeam.h index 76e6ae51..a75059f1 100644 --- a/src/hal/ttgobeam.h +++ b/src/hal/ttgobeam.h @@ -30,7 +30,7 @@ // BME680 sensor on I2C bus //#define HAS_BME 1 // Enable BME sensors in general //#define HAS_BME680 SDA, SCL -//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! +//#define BME680_ADDR BME68X_I2C_ADDR_LOW // !! connect SDIO of BME680 to GND !! // display (if connected) //#define HAS_DISPLAY 1 diff --git a/src/hal/ttgobeam10.h b/src/hal/ttgobeam10.h index 9dbfe173..dbceeba6 100644 --- a/src/hal/ttgobeam10.h +++ b/src/hal/ttgobeam10.h @@ -52,7 +52,7 @@ Reset -> reset device // BME680 sensor on I2C bus //#define HAS_BME 1 // Enable BME sensors in general //#define HAS_BME680 SDA, SCL -//#define BME680_ADDR BME680_I2C_ADDR_PRIMARY // !! connect SDIO of BME680 to GND !! +//#define BME680_ADDR BME68X_I2C_ADDR_LOW // !! connect SDIO of BME680 to GND !! //#define DISABLE_BROWNOUT 1 // comment out if you want to keep brownout feature diff --git a/src/rcommand.cpp b/src/rcommand.cpp index fbdb833f..21639cf7 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -2,7 +2,6 @@ #include "globals.h" #include "rcommand.h" - static QueueHandle_t RcmdQueue; TaskHandle_t rcmdTask; @@ -59,7 +58,7 @@ void set_rssi(uint8_t val[]) { current_config.ble_rssi_threshold = cfg.rssilimit; libpax_update_config(¤t_config); init_libpax(); - ESP_LOGI(TAG, "Remote command: set RSSI limit to %d", cfg.rssilimit); + ESP_LOGI(TAG, "Remote command: set RSSI limit to %hd", cfg.rssilimit); } void set_sendcycle(uint8_t val[]) { @@ -67,7 +66,7 @@ void set_sendcycle(uint8_t val[]) { return; // update send cycle interrupt [seconds / 2] cfg.sendcycle = val[0]; - ESP_LOGI(TAG, "Remote command: set send cycle to %d seconds", + ESP_LOGI(TAG, "Remote command: set send cycle to %u seconds", cfg.sendcycle * 2); libpax_counter_stop(); init_libpax(); @@ -76,10 +75,16 @@ void set_sendcycle(uint8_t val[]) { void set_sleepcycle(uint8_t val[]) { // swap byte order from msb to lsb, note: this is a platform dependent hack cfg.sleepcycle = __builtin_bswap16(*(uint16_t *)(val)); - ESP_LOGI(TAG, "Remote command: set sleep cycle to %d seconds", + ESP_LOGI(TAG, "Remote command: set sleep cycle to %hu seconds", cfg.sleepcycle * 10); } +void set_wakesync(uint8_t val[]) { + // swap byte order from msb to lsb, note: this is a platform dependent hack + cfg.wakesync = __builtin_bswap16(*(uint16_t *)(val)); + ESP_LOGI(TAG, "Remote command: set wakesync to %hu seconds", cfg.wakesync); +} + void set_wifichancycle(uint8_t val[]) { cfg.wifichancycle = val[0]; libpax_counter_stop(); @@ -193,7 +198,7 @@ void set_sensor(uint8_t val[]) { return; // invalid sensor number -> exit } - ESP_LOGI(TAG, "Remote command: set sensor #%d mode to %s", val[0], + ESP_LOGI(TAG, "Remote command: set sensor #%u mode to %s", val[0], val[1] ? "on" : "off"); if (val[1]) @@ -213,7 +218,7 @@ void set_loradr(uint8_t val[]) { #if (HAS_LORA) if (validDR(val[0])) { cfg.loradr = val[0]; - ESP_LOGI(TAG, "Remote command: set LoRa Datarate to %d", cfg.loradr); + ESP_LOGI(TAG, "Remote command: set LoRa Datarate to %u", cfg.loradr); LMIC_setDrTxpow(assertDR(cfg.loradr), KEEP_TXPOW); ESP_LOGI(TAG, "Radio parameters now %s / %s / %s", getSfName(updr2rps(LMIC.datarate)), @@ -222,7 +227,7 @@ void set_loradr(uint8_t val[]) { } else ESP_LOGI( TAG, - "Remote command: set LoRa Datarate called with illegal datarate %d", + "Remote command: set LoRa Datarate called with illegal datarate %u", val[0]); #else ESP_LOGW(TAG, "Remote command: LoRa not implemented"); @@ -275,15 +280,15 @@ void set_wifiant(uint8_t val[]) { void set_rgblum(uint8_t val[]) { // Avoid wrong parameters cfg.rgblum = (val[0] <= 100) ? (uint8_t)val[0] : RGBLUMINOSITY; - ESP_LOGI(TAG, "Remote command: set RGB Led luminosity %d", cfg.rgblum); -}; + ESP_LOGI(TAG, "Remote command: set RGB Led luminosity %u", cfg.rgblum); +} void set_lorapower(uint8_t val[]) { #if (HAS_LORA) // set data rate and transmit power only if we have no ADR if (!cfg.adrmode) { cfg.txpower = val[0]; - ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %d", cfg.txpower); + ESP_LOGI(TAG, "Remote command: set LoRa TXPOWER to %u", cfg.txpower); LMIC_setDrTxpow(assertDR(cfg.loradr), cfg.txpower); } else ESP_LOGI( @@ -293,14 +298,14 @@ void set_lorapower(uint8_t val[]) { #else ESP_LOGW(TAG, "Remote command: LoRa not implemented"); #endif // HAS_LORA -}; +} void get_config(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get device configuration"); payload.reset(); payload.addConfig(cfg); SendPayload(CONFIGPORT); -}; +} void get_status(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get device status"); @@ -315,7 +320,7 @@ void get_status(uint8_t val[]) { RTC_restarts); #endif SendPayload(STATUSPORT); -}; +} void get_gps(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get gps status"); @@ -328,7 +333,7 @@ void get_gps(uint8_t val[]) { #else ESP_LOGW(TAG, "GPS function not supported"); #endif -}; +} void get_bme(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get bme680 sensor data"); @@ -339,7 +344,7 @@ void get_bme(uint8_t val[]) { #else ESP_LOGW(TAG, "BME sensor not supported"); #endif -}; +} void get_batt(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get battery voltage"); @@ -350,7 +355,7 @@ void get_batt(uint8_t val[]) { #else ESP_LOGW(TAG, "Battery voltage not supported"); #endif -}; +} void get_time(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: get time"); @@ -359,35 +364,35 @@ void get_time(uint8_t val[]) { payload.addTime(t); payload.addByte(sntp_get_sync_status() << 4 | timeSource); SendPayload(TIMEPORT); -}; +} void set_timesync(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: timesync requested"); setTimeSyncIRQ(); -}; +} void set_time(uint8_t val[]) { // swap byte order from msb to lsb, note: this is a platform dependent hack uint32_t t = __builtin_bswap32(*(uint32_t *)(val)); - ESP_LOGI(TAG, "Remote command: set time to %d", t); + ESP_LOGI(TAG, "Remote command: set time to %lu", t); setMyTime(t, 0, _set); -}; +} void set_flush(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: flush"); // does nothing // used to open receive window on LoRaWAN class a nodes -}; +} void set_loadconfig(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: load config from NVRAM"); loadConfig(); -}; +} void set_saveconfig(uint8_t val[]) { ESP_LOGI(TAG, "Remote command: save config to NVRAM"); saveConfig(false); -}; +} // assign previously defined functions to set of numeric remote commands // format: {opcode, function, number of function arguments} @@ -399,17 +404,17 @@ static const cmd_t table[] = { {0x07, set_loraadr, 1}, {0x08, set_screensaver, 1}, {0x09, set_reset, 1}, {0x0a, set_sendcycle, 1}, {0x0b, set_wifichancycle, 1}, {0x0c, set_blescantime, 1}, - {0x0e, set_blescan, 1}, {0x0f, set_wifiant, 1}, - {0x10, set_rgblum, 1}, {0x13, set_sensor, 2}, - {0x14, set_payloadmask, 1}, {0x15, set_bme, 1}, - {0x16, set_batt, 1}, {0x17, set_wifiscan, 1}, - {0x18, set_flush, 0}, {0x19, set_sleepcycle, 2}, - {0x20, set_loadconfig, 0}, {0x21, set_saveconfig, 0}, - {0x80, get_config, 0}, {0x81, get_status, 0}, - {0x83, get_batt, 0}, {0x84, get_gps, 0}, - {0x85, get_bme, 0}, {0x86, get_time, 0}, - {0x87, set_timesync, 0}, {0x88, set_time, 4}, - {0x99, set_flush, 0}}; + {0x0d, set_wakesync, 2}, {0x0e, set_blescan, 1}, + {0x0f, set_wifiant, 1}, {0x10, set_rgblum, 1}, + {0x13, set_sensor, 2}, {0x14, set_payloadmask, 1}, + {0x15, set_bme, 1}, {0x16, set_batt, 1}, + {0x17, set_wifiscan, 1}, {0x18, set_flush, 0}, + {0x19, set_sleepcycle, 2}, {0x20, set_loadconfig, 0}, + {0x21, set_saveconfig, 0}, {0x80, get_config, 0}, + {0x81, get_status, 0}, {0x83, get_batt, 0}, + {0x84, get_gps, 0}, {0x85, get_bme, 0}, + {0x86, get_time, 0}, {0x87, set_timesync, 0}, + {0x88, set_time, 4}, {0x99, set_flush, 0}}; static const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table @@ -492,7 +497,7 @@ esp_err_t rcmd_init(void) { ESP_LOGE(TAG, "Could not create rcommand send queue. Aborting."); return ESP_FAIL; } - ESP_LOGI(TAG, "Rcommand send queue created, size %d Bytes", + ESP_LOGI(TAG, "Rcommand send queue created, size %u Bytes", RCMD_QUEUE_SIZE * sizeof(RcmdBuffer_t)); xTaskCreatePinnedToCore(rcmd_process, // task function diff --git a/src/reset.cpp b/src/reset.cpp index 13ec8afc..51d463ca 100644 --- a/src/reset.cpp +++ b/src/reset.cpp @@ -19,6 +19,32 @@ void reset_rtc_vars(void) { RTC_restarts = 0; } +#if (HAS_TIME) +void adjust_wakeup(uint32_t *wakeuptime) { + // only adjust wakeup if we have a valid time + if ((timeSource == _unsynced) || + (sntp_get_sync_status() == SNTP_SYNC_STATUS_IN_PROGRESS)) { + ESP_LOGI(TAG, "Syncwakeup: No valid time for sync"); + return; + } + + time_t now; + time(&now); + + // 1..3600 seconds between next wakeup time and following top-of-hour + uint16_t shift_sec = 3600 - (now + *wakeuptime) % 3600; + + if (shift_sec <= SYNCWAKEUP) { + *wakeuptime += shift_sec; // delay wakeup to catch top-of-hour + ESP_LOGI(TAG, "Syncwakeup: Wakeup %hu sec postponed", shift_sec); + } else if (shift_sec >= (3600 - SYNCWAKEUP)) { + *wakeuptime = 3600 - shift_sec; // shorten wake up to next top-of-hour + ESP_LOGI(TAG, "Syncwakeup: Wakeup %hu sec preponed", shift_sec); + } else + ESP_LOGI(TAG, "Syncwakeup: Wakeup keeping unshifted"); +} +#endif + void do_reset(bool warmstart) { if (warmstart) { ESP_LOGI(TAG, "restarting device (warmstart)"); @@ -145,31 +171,14 @@ void enter_deepsleep(uint32_t wakeup_sec, gpio_num_t wakeup_gpio) { // shutdown i2c bus i2c_deinit(); +#if (HAS_TIME) + if (cfg.wakesync && cfg.sleepcycle) + adjust_wakeup(&wakeup_sec); +#endif + // configure wakeup sources // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html -#if (HAS_TIME) -#if (SYNCWAKEUP) && (SLEEPCYCLE) - if ((timeSource != _unsynced) && - (sntp_get_sync_status() != - SNTP_SYNC_STATUS_IN_PROGRESS)) { // only sync if we have a valid time - time_t now; - time(&now); - - // 1..3600 seconds between next wakeup time and following top-of-hour - uint16_t shift_sec = 3600 - (now + wakeup_sec) % 3600; - - if (shift_sec <= SYNCWAKEUP) { - wakeup_sec += shift_sec; // delay wakeup to catch top-of-hour - ESP_LOGI(TAG, "Syncwakeup: Wakeup %hu sec postponed", shift_sec); - } else if (shift_sec >= (3600 - SYNCWAKEUP)) { - wakeup_sec = 3600 - shift_sec; // shorten wake up to next top-of-hour - ESP_LOGI(TAG, "Syncwakeup: Wakeup %hu sec preponed", shift_sec); - } - } -#endif -#endif - // set up RTC wakeup timer, if we have if (wakeup_sec > 0) { esp_sleep_enable_timer_wakeup(wakeup_sec * uS_TO_S_FACTOR); diff --git a/src/timekeeper.cpp b/src/timekeeper.cpp index 4b9c69ab..4412990d 100644 --- a/src/timekeeper.cpp +++ b/src/timekeeper.cpp @@ -6,8 +6,8 @@ // symbol to display current time source -// G = GPS / R = RTC / L = LORA / * = no sync / ? = never synced -const char timeSetSymbols[] = {'G', 'R', 'L', '*', '?'}; +// G = GPS / R = RTC / L = LORA / ? = unsynced / * = set +const char timeSetSymbols[] = {'G', 'R', 'L', '?', '*'}; DRAM_ATTR bool TimePulseTick = false; #ifdef GPS_INT