new feature deep sleep (wokring alpha)

This commit is contained in:
Klaus K Wilting 2020-12-09 10:15:12 +01:00
parent 016d69b5bb
commit 36afe66df9
12 changed files with 262 additions and 179 deletions

View File

@ -20,9 +20,10 @@
extern TaskHandle_t lmicTask, lorasendTask; extern TaskHandle_t lmicTask, lorasendTask;
void lora_stack_reset(); esp_err_t lmic_init(void);
esp_err_t lora_stack_init(bool do_join);
void lora_setupForNetwork(bool preJoin); void lora_setupForNetwork(bool preJoin);
void SaveLMICToRTC(int deepsleep_sec);
void LoadLMICFromRTC();
void lmictask(void *pvParameters); void lmictask(void *pvParameters);
void gen_lora_deveui(uint8_t *pdeveui); void gen_lora_deveui(uint8_t *pdeveui);
void RevBytes(unsigned char *b, size_t c); void RevBytes(unsigned char *b, size_t c);
@ -33,6 +34,7 @@ void os_getDevEui(u1_t *buf);
void lora_send(void *pvParameters); void lora_send(void *pvParameters);
void lora_enqueuedata(MessageBuffer_t *message); void lora_enqueuedata(MessageBuffer_t *message);
void lora_queuereset(void); void lora_queuereset(void);
uint32_t lora_queuewaiting(void);
uint8_t myBattLevelCb(void *pUserData); uint8_t myBattLevelCb(void *pUserData);
void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev); void IRAM_ATTR myEventCallback(void *pUserData, ev_t ev);
void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg, void IRAM_ATTR myRxCallback(void *pUserData, uint8_t port, const uint8_t *pMsg,

View File

@ -18,7 +18,8 @@ extern Ticker sendTimer;
void SendPayload(uint8_t port, sendprio_t prio); void SendPayload(uint8_t port, sendprio_t prio);
void sendData(void); void sendData(void);
void checkSendQueues(void); void checkSendQueues(void);
void flushQueues(); void flushQueues(void);
bool allQueuesEmtpy(void);
void setSendIRQ(void); void setSendIRQ(void);
#endif // _SENDDATA_H_ #endif // _SENDDATA_H_

View File

@ -7,7 +7,7 @@
; ---> SELECT THE TARGET PLATFORM HERE! <--- ; ---> SELECT THE TARGET PLATFORM HERE! <---
[board] [board]
halfile = generic.h ;halfile = generic.h
;halfile = ebox.h ;halfile = ebox.h
;halfile = eboxtube.h ;halfile = eboxtube.h
;halfile = ecopower.h ;halfile = ecopower.h
@ -19,7 +19,7 @@ halfile = generic.h
;halfile = ttgov21new.h ;halfile = ttgov21new.h
;halfile = ttgofox.h ;halfile = ttgofox.h
;halfile = ttgobeam.h ;halfile = ttgobeam.h
;halfile = ttgobeam10.h halfile = ttgobeam10.h
;halfile = ttgotdisplay.h ;halfile = ttgotdisplay.h
;halfile = fipy.h ;halfile = fipy.h
;halfile = lopy.h ;halfile = lopy.h
@ -47,7 +47,7 @@ description = Paxcounter is a device for metering passenger flows in realtime. I
[common] [common]
; for release_version use max. 10 chars total, use any decimal format like "a.b.c" ; for release_version use max. 10 chars total, use any decimal format like "a.b.c"
release_version = 2.0.4 release_version = 2.0.51
; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running!
; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose
debug_level = 3 debug_level = 3
@ -55,7 +55,7 @@ extra_scripts = pre:build.py
otakeyfile = ota.conf otakeyfile = ota.conf
lorakeyfile = loraconf.h lorakeyfile = loraconf.h
lmicconfigfile = lmic_config.h lmicconfigfile = lmic_config.h
platform_espressif32 = espressif32@2.0.0 platform_espressif32 = espressif32@2.1.0
monitor_speed = 115200 monitor_speed = 115200
upload_speed = 115200 ; set by build.py and taken from hal file upload_speed = 115200 ; set by build.py and taken from hal file
display_library = ; set by build.py and taken from hal file display_library = ; set by build.py and taken from hal file
@ -65,7 +65,7 @@ lib_deps_display =
bitbank2/OneBitDisplay @ 1.9.0 bitbank2/OneBitDisplay @ 1.9.0
bitbank2/BitBang_I2C @ ^2.1.3 bitbank2/BitBang_I2C @ ^2.1.3
ricmoo/QRCode @ ^0.0.1 ricmoo/QRCode @ ^0.0.1
bodmer/TFT_eSPI @ ^2.2.20 bodmer/TFT_eSPI @ ^2.3.51
lib_deps_ledmatrix = lib_deps_ledmatrix =
seeed-studio/Ultrathin_LED_Matrix @ ^1.0.0 seeed-studio/Ultrathin_LED_Matrix @ ^1.0.0
lib_deps_rgbled = lib_deps_rgbled =
@ -76,8 +76,7 @@ lib_deps_sensors =
adafruit/Adafruit Unified Sensor @ ^1.1.4 adafruit/Adafruit Unified Sensor @ ^1.1.4
adafruit/Adafruit BME280 Library @ ^2.1.1 adafruit/Adafruit BME280 Library @ ^2.1.1
adafruit/Adafruit BMP085 Library @ ^1.1.0 adafruit/Adafruit BMP085 Library @ ^1.1.0
;boschsensortec/BSEC Software Library @ 1.6.1480 boschsensortec/BSEC Software Library @ 1.6.1480
https://github.com/BoschSensortec/BSEC-Arduino-library.git
https://github.com/ricki-z/SDS011.git https://github.com/ricki-z/SDS011.git
lib_deps_basic = lib_deps_basic =
bblanchon/ArduinoJson @ <6 bblanchon/ArduinoJson @ <6
@ -117,7 +116,7 @@ framework = arduino
board = esp32dev board = esp32dev
board_build.partitions = min_spiffs.csv board_build.partitions = min_spiffs.csv
upload_speed = ${common.upload_speed} upload_speed = ${common.upload_speed}
;upload_port = COM8 ;upload_port = COM3
platform = ${common.platform_espressif32} platform = ${common.platform_espressif32}
lib_deps = ${common.lib_deps_all} lib_deps = ${common.lib_deps_all}
build_flags = ${common.build_flags_all} build_flags = ${common.build_flags_all}

View File

@ -95,6 +95,13 @@ void irqHandler(void *pvParameters) {
if (InterruptStatus & SENDCYCLE_IRQ) { if (InterruptStatus & SENDCYCLE_IRQ) {
sendData(); sendData();
InterruptStatus &= ~SENDCYCLE_IRQ; InterruptStatus &= ~SENDCYCLE_IRQ;
// goto sleep if we have a sleep cycle
if (cfg.sleepcycle)
#ifdef HAS_BUTTON
enter_deepsleep(cfg.sleepcycle * 2, HAS_BUTTON);
#else
enter_deepsleep(cfg.sleepcycle * 2);
#endif
} }
} // for } // for
} // irqHandler() } // irqHandler()

View File

@ -6,6 +6,9 @@
// Local logging Tag // Local logging Tag
static const char TAG[] = "lora"; static const char TAG[] = "lora";
// Saves the LMIC structure during deep sleep
RTC_DATA_ATTR lmic_t RTC_LMIC;
#if CLOCK_ERROR_PROCENTAGE > 7 #if CLOCK_ERROR_PROCENTAGE > 7
#warning CLOCK_ERROR_PROCENTAGE value in lmic_config.h is too high; values > 7 will cause side effects #warning CLOCK_ERROR_PROCENTAGE value in lmic_config.h is too high; values > 7 will cause side effects
#endif #endif
@ -16,11 +19,6 @@ static const char TAG[] = "lora";
#endif #endif
#endif #endif
// variable keep its values after restart or wakeup from sleep
RTC_NOINIT_ATTR u4_t RTCnetid, RTCdevaddr;
RTC_NOINIT_ATTR u1_t RTCnwkKey[16], RTCartKey[16];
RTC_NOINIT_ATTR int RTCseqnoUp, RTCseqnoDn;
QueueHandle_t LoraSendQueue; QueueHandle_t LoraSendQueue;
TaskHandle_t lmicTask = NULL, lorasendTask = NULL; TaskHandle_t lmicTask = NULL, lorasendTask = NULL;
@ -83,8 +81,6 @@ void lora_setupForNetwork(bool preJoin) {
getSfName(updr2rps(LMIC.datarate)), getSfName(updr2rps(LMIC.datarate)),
getBwName(updr2rps(LMIC.datarate)), getBwName(updr2rps(LMIC.datarate)),
getCrName(updr2rps(LMIC.datarate))); getCrName(updr2rps(LMIC.datarate)));
// store LMIC keys and counters in RTC memory
LMIC_getSessionKeys(&RTCnetid, &RTCdevaddr, RTCnwkKey, RTCartKey);
} }
} }
@ -197,59 +193,47 @@ void lora_send(void *pvParameters) {
} }
// fetch next or wait for payload to send from queue // fetch next or wait for payload to send from queue
if (xQueueReceive(LoraSendQueue, &SendBuffer, portMAX_DELAY) != pdTRUE) { // do not delete item from queue until it is transmitted
if (xQueuePeek(LoraSendQueue, &SendBuffer, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!");
continue; continue;
} }
// attempt to transmit payload // attempt to transmit payload
else { switch (LMIC_setTxData2_strict(SendBuffer.MessagePort, SendBuffer.Message,
SendBuffer.MessageSize,
switch (LMIC_setTxData2_strict(SendBuffer.MessagePort, SendBuffer.Message, (cfg.countermode & 0x02))) {
SendBuffer.MessageSize,
(cfg.countermode & 0x02))) {
case LMIC_ERROR_SUCCESS:
// save current Fcnt to RTC RAM
RTCseqnoUp = LMIC.seqnoUp;
RTCseqnoDn = LMIC.seqnoDn;
case LMIC_ERROR_SUCCESS:
#if (TIME_SYNC_LORASERVER) #if (TIME_SYNC_LORASERVER)
// if last packet sent was a timesync request, store TX timestamp // if last packet sent was a timesync request, store TX timestamp
if (SendBuffer.MessagePort == TIMEPORT) if (SendBuffer.MessagePort == TIMEPORT)
// store LMIC time when we started transmit of timesync request // store LMIC time when we started transmit of timesync request
timesync_store(osticks2ms(os_getTime()), timesync_tx); timesync_store(osticks2ms(os_getTime()), timesync_tx);
#endif #endif
ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize);
// delete sent item from queue
xQueueReceive(LoraSendQueue, &SendBuffer, (TickType_t)0);
break;
case LMIC_ERROR_TX_BUSY: // LMIC already has a tx message pending
case LMIC_ERROR_TX_FAILED: // message was not sent
vTaskDelay(pdMS_TO_TICKS(500 + random(400))); // wait a while
break;
case LMIC_ERROR_TX_TOO_LARGE: // message size exceeds LMIC buffer size
case LMIC_ERROR_TX_NOT_FEASIBLE: // message too large for current
// datarate
ESP_LOGI(TAG, "Message too large to send, message not sent and deleted");
// we need some kind of error handling here -> to be done
break;
default: // other LMIC return code
ESP_LOGE(TAG, "LMIC error, message not sent and deleted");
ESP_LOGI(TAG, "%d byte(s) sent to LORA", SendBuffer.MessageSize); } // switch
break;
case LMIC_ERROR_TX_BUSY: // LMIC already has a tx message pending
case LMIC_ERROR_TX_FAILED: // message was not sent
// ESP_LOGD(TAG, "LMIC busy, message re-enqueued"); // very noisy
vTaskDelay(pdMS_TO_TICKS(1000 + random(500))); // wait a while
lora_enqueuedata(&SendBuffer); // re-enqueue the undelivered message
break;
case LMIC_ERROR_TX_TOO_LARGE: // message size exceeds LMIC buffer size
case LMIC_ERROR_TX_NOT_FEASIBLE: // message too large for current
// datarate
ESP_LOGI(TAG,
"Message too large to send, message not sent and deleted");
// we need some kind of error handling here -> to be done
break;
default: // other LMIC return code
ESP_LOGE(TAG, "LMIC error, message not sent and deleted");
} // switch
}
delay(2); // yield to CPU delay(2); // yield to CPU
} } // while(1)
} }
void lora_stack_reset() { esp_err_t lmic_init(void) {
LMIC_reset(); // reset LMIC MAC
}
esp_err_t lora_stack_init(bool do_join) {
_ASSERT(SEND_QUEUE_SIZE > 0); _ASSERT(SEND_QUEUE_SIZE > 0);
LoraSendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t)); LoraSendQueue = xQueueCreate(SEND_QUEUE_SIZE, sizeof(MessageBuffer_t));
if (LoraSendQueue == 0) { if (LoraSendQueue == 0) {
@ -259,7 +243,58 @@ esp_err_t lora_stack_init(bool do_join) {
ESP_LOGI(TAG, "LORA send queue created, size %d Bytes", ESP_LOGI(TAG, "LORA send queue created, size %d Bytes",
SEND_QUEUE_SIZE * sizeof(MessageBuffer_t)); SEND_QUEUE_SIZE * sizeof(MessageBuffer_t));
// start lorawan stack // setup LMIC stack
os_init_ex(&myPinmap); // initialize lmic run-time environment
// register a callback for downlink messages and lmic events.
// We aren't trying to write reentrant code, so pUserData is NULL.
// LMIC_reset() doesn't affect callbacks, so we can do this first.
LMIC_registerRxMessageCb(myRxCallback, NULL);
LMIC_registerEventCb(myEventCallback, NULL);
// to come with future LMIC version
// LMIC_registerBattLevelCb(myBattLevelCb, NULL);
// Reset the MAC state. Session and pending data transfers will be
// discarded.
LMIC_reset();
// This tells LMIC to make the receive windows bigger, in case your clock is
// faster or slower. This causes the transceiver to be earlier switched on,
// so consuming more power. You may sharpen (reduce) CLOCK_ERROR_PERCENTAGE
// in src/lmic_config.h if you are limited on battery.
#ifdef CLOCK_ERROR_PROCENTAGE
LMIC_setClockError(CLOCK_ERROR_PROCENTAGE * MAX_CLOCK_ERROR / 1000);
#endif
// Pass ABP parameters to LMIC_setSession
#ifdef LORA_ABP
setABPParameters(); // These parameters are defined as macro in loraconf.h
// load saved session from RTC, if we have one
if (RTC_runmode == RUNMODE_WAKEUP) {
LoadLMICFromRTC();
} else {
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession(NETID, DEVADDR, nwkskey, appskey);
}
// Pass OTA parameters to LMIC_setSession
#else
// load saved session from RTC, if we have one
if (RTC_runmode == RUNMODE_WAKEUP) {
LoadLMICFromRTC();
}
// otherwise start join procedure if not already joined
else {
if (!LMIC_startJoining())
ESP_LOGI(TAG, "Already joined");
}
#endif
// start lmic loop task
ESP_LOGI(TAG, "Starting LMIC..."); ESP_LOGI(TAG, "Starting LMIC...");
xTaskCreatePinnedToCore(lmictask, // task function xTaskCreatePinnedToCore(lmictask, // task function
"lmictask", // name of task "lmictask", // name of task
@ -269,31 +304,7 @@ esp_err_t lora_stack_init(bool do_join) {
&lmicTask, // task handle &lmicTask, // task handle
1); // CPU core 1); // CPU core
#ifdef LORA_ABP // start lora send task
// Pass ABP parameters to LMIC_setSession
lora_stack_reset();
uint8_t appskey[sizeof(APPSKEY)];
uint8_t nwkskey[sizeof(NWKSKEY)];
memcpy_P(appskey, APPSKEY, sizeof(APPSKEY));
memcpy_P(nwkskey, NWKSKEY, sizeof(NWKSKEY));
LMIC_setSession(NETID, DEVADDR, nwkskey, appskey);
// These parameters are defined as macro in loraconf.h
setABPParameters();
#else
// Start join procedure if not already joined,
// lora_setupForNetwork(true) is called by eventhandler when joined
// else continue current session
if (do_join) {
if (!LMIC_startJoining())
ESP_LOGI(TAG, "Already joined");
} else {
lora_stack_reset();
LMIC_setSession(RTCnetid, RTCdevaddr, RTCnwkKey, RTCartKey);
LMIC.seqnoUp = RTCseqnoUp;
LMIC.seqnoDn = RTCseqnoDn;
}
#endif
// start lmic send task
xTaskCreatePinnedToCore(lora_send, // task function xTaskCreatePinnedToCore(lora_send, // task function
"lorasendtask", // name of task "lorasendtask", // name of task
3072, // stack size of task 3072, // stack size of task
@ -319,11 +330,11 @@ void lora_enqueuedata(MessageBuffer_t *message) {
ESP_LOGW(TAG, "LORA sendqueue purged, data is lost"); ESP_LOGW(TAG, "LORA sendqueue purged, data is lost");
} }
case prio_normal: case prio_normal:
ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0); ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0);
break; break;
case prio_low: case prio_low:
default: default:
ret = xQueueSendToBack(LoraSendQueue, (void *)message, (TickType_t)0); ret = xQueueSendToFront(LoraSendQueue, (void *)message, (TickType_t)0);
break; break;
} }
if (ret != pdTRUE) { if (ret != pdTRUE) {
@ -338,38 +349,18 @@ void lora_enqueuedata(MessageBuffer_t *message) {
void lora_queuereset(void) { xQueueReset(LoraSendQueue); } void lora_queuereset(void) { xQueueReset(LoraSendQueue); }
// LMIC lorawan stack task uint32_t lora_queuewaiting(void) {
return uxQueueMessagesWaiting(LoraSendQueue);
}
// LMIC loop task
void lmictask(void *pvParameters) { void lmictask(void *pvParameters) {
_ASSERT((uint32_t)pvParameters == 1); _ASSERT((uint32_t)pvParameters == 1);
// setup LMIC stack
os_init_ex(&myPinmap); // initialize lmic run-time environment
// register a callback for downlink messages and lmic events.
// We aren't trying to write reentrant code, so pUserData is NULL.
// LMIC_reset() doesn't affect callbacks, so we can do this first.
LMIC_registerRxMessageCb(myRxCallback, NULL);
LMIC_registerEventCb(myEventCallback, NULL);
// to come with future LMIC version
// LMIC_registerBattLevelCb(myBattLevelCb, NULL);
// Reset the MAC state. Session and pending data transfers will be
// discarded.
lora_stack_reset();
// This tells LMIC to make the receive windows bigger, in case your clock is
// faster or slower. This causes the transceiver to be earlier switched on,
// so consuming more power. You may sharpen (reduce) CLOCK_ERROR_PERCENTAGE
// in src/lmic_config.h if you are limited on battery.
#ifdef CLOCK_ERROR_PROCENTAGE
LMIC_setClockError(CLOCK_ERROR_PROCENTAGE * MAX_CLOCK_ERROR / 1000);
#endif
while (1) { while (1) {
os_runloop_once(); // execute lmic scheduled jobs and events os_runloop_once(); // execute lmic scheduled jobs and events
delay(2); // yield to CPU delay(2); // yield to CPU
} }
} // lmictask }
// lmic event handler // lmic event handler
void myEventCallback(void *pUserData, ev_t ev) { void myEventCallback(void *pUserData, ev_t ev) {
@ -410,7 +401,7 @@ void myEventCallback(void *pUserData, ev_t ev) {
case EV_JOIN_FAILED: case EV_JOIN_FAILED:
// must call LMIC_reset() to stop joining // must call LMIC_reset() to stop joining
// otherwise join procedure continues. // otherwise join procedure continues.
lora_stack_reset(); LMIC_reset();
break; break;
case EV_JOIN_TXCOMPLETE: case EV_JOIN_TXCOMPLETE:
@ -560,4 +551,44 @@ void mac_decode(const uint8_t cmd[], const uint8_t cmdlen, bool is_down) {
} // mac_decode() } // mac_decode()
#endif // VERBOSE #endif // VERBOSE
// following code snippet was taken from
// https://github.com/JackGruber/ESP32-LMIC-DeepSleep-example/blob/master/src/main.cpp
void SaveLMICToRTC(int deepsleep_sec) {
RTC_LMIC = LMIC;
// ESP32 can't track millis during DeepSleep and no option to advance
// millis after DeepSleep. Therefore reset DutyCyles
unsigned long now = millis();
// EU Like Bands
#if defined(CFG_LMIC_EU_like)
for (int i = 0; i < MAX_BANDS; i++) {
ostime_t correctedAvail =
RTC_LMIC.bands[i].avail -
((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC);
if (correctedAvail < 0) {
correctedAvail = 0;
}
RTC_LMIC.bands[i].avail = correctedAvail;
}
RTC_LMIC.globalDutyAvail = RTC_LMIC.globalDutyAvail -
((now / 1000.0 + deepsleep_sec) * OSTICKS_PER_SEC);
if (RTC_LMIC.globalDutyAvail < 0) {
RTC_LMIC.globalDutyAvail = 0;
}
#else
ESP_LOGW(TAG, "No DutyCycle recalculation function!");
#endif
ESP_LOGI(TAG, "LMIC state saved");
}
void LoadLMICFromRTC() {
LMIC = RTC_LMIC;
ESP_LOGI(TAG, "LMIC state loaded");
}
#endif // HAS_LORA #endif // HAS_LORA

View File

@ -348,9 +348,7 @@ void setup() {
// initialize LoRa // initialize LoRa
#if (HAS_LORA) #if (HAS_LORA)
strcat_P(features, " LORA"); strcat_P(features, " LORA");
// kick off join, except we come from sleep _ASSERT(lmic_init() == ESP_OK);
_ASSERT(lora_stack_init(RTC_runmode == RUNMODE_WAKEUP ? false : true) ==
ESP_OK);
#endif #endif
// initialize SPI // initialize SPI

View File

@ -115,7 +115,8 @@ void mqtt_client_task(void *param) {
while (1) { while (1) {
// fetch next or wait for payload to send from queue // fetch next or wait for payload to send from queue
if (xQueueReceive(MQTTSendQueue, &msg, portMAX_DELAY) != pdTRUE) { // do not delete item from queue until it is transmitted
if (xQueuePeek(MQTTSendQueue, &msg, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!");
continue; continue;
} }
@ -129,19 +130,17 @@ void mqtt_client_task(void *param) {
if (mqttClient.publish(MQTT_OUTTOPIC, buffer)) { if (mqttClient.publish(MQTT_OUTTOPIC, buffer)) {
ESP_LOGI(TAG, "%d byte(s) sent to MQTT server", msg.MessageSize + 2); ESP_LOGI(TAG, "%d byte(s) sent to MQTT server", msg.MessageSize + 2);
// delete sent item from queue
xQueueReceive(MQTTSendQueue, &msg, (TickType_t)0);
continue; continue;
} else { } else
mqtt_enqueuedata(&msg); // postpone the undelivered message ESP_LOGD(TAG, "Couldn't sent message to MQTT server");
ESP_LOGD(TAG,
"Couldn't sent message to MQTT server, message postponed");
}
} else { } else {
// attempt to reconnect to MQTT server // attempt to reconnect to MQTT server
ESP_LOGD(TAG, "MQTT client reconnecting..."); ESP_LOGD(TAG, "MQTT client reconnecting...");
ESP_LOGD(TAG, "MQTT last_error = %d / rc = %d", mqttClient.lastError(), ESP_LOGD(TAG, "MQTT last_error = %d / rc = %d", mqttClient.lastError(),
mqttClient.returnCode()); mqttClient.returnCode());
mqtt_enqueuedata(&msg); // postpone the undelivered message
delay(MQTT_RETRYSEC * 1000); delay(MQTT_RETRYSEC * 1000);
mqtt_connect(MQTT_SERVER, MQTT_PORT); mqtt_connect(MQTT_SERVER, MQTT_PORT);
} }
@ -161,11 +160,11 @@ void mqtt_enqueuedata(MessageBuffer_t *message) {
if (!uxQueueSpacesAvailable(MQTTSendQueue)) if (!uxQueueSpacesAvailable(MQTTSendQueue))
xQueueReceive(MQTTSendQueue, &DummyBuffer, (TickType_t)0); xQueueReceive(MQTTSendQueue, &DummyBuffer, (TickType_t)0);
case prio_normal: case prio_normal:
ret = xQueueSendToFront(MQTTSendQueue, (void *)message, (TickType_t)0); ret = xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0);
break; break;
case prio_low: case prio_low:
default: default:
ret = xQueueSendToBack(MQTTSendQueue, (void *)message, (TickType_t)0); ret = xQueueSendToFront(MQTTSendQueue, (void *)message, (TickType_t)0);
break; break;
} }
if (ret != pdTRUE) if (ret != pdTRUE)
@ -185,6 +184,11 @@ void mqtt_loop(void) {
} }
void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); } void mqtt_queuereset(void) { xQueueReset(MQTTSendQueue); }
uint32_t mqtt_queuewaiting(void) {
return uxQueueMessagesWaitingMQTTSendQueue);
}
void setMqttIRQ(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); } void setMqttIRQ(void) { xTaskNotify(irqHandlerTask, MQTT_IRQ, eSetBits); }
#endif // HAS_MQTT #endif // HAS_MQTT

View File

@ -11,12 +11,13 @@
// Payload send cycle and encoding // Payload send cycle and encoding
#define SENDCYCLE 30 // payload send cycle [seconds/2], 0 .. 255 #define SENDCYCLE 30 // payload send cycle [seconds/2], 0 .. 255
#define SLEEPCYCLE 0 // sleep time after a send cycle [seconds/2], 0 .. 255; 0 means no sleep [default = 0]
#define PAYLOAD_ENCODER 2 // payload encoder: 1=Plain, 2=Packed, 3=Cayenne LPP dynamic, 4=Cayenne LPP packed #define PAYLOAD_ENCODER 2 // payload encoder: 1=Plain, 2=Packed, 3=Cayenne LPP dynamic, 4=Cayenne LPP packed
#define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed #define COUNTERMODE 0 // 0=cyclic, 1=cumulative, 2=cyclic confirmed
// MAC sniffing parameters // MAC sniffing parameters
#define VENDORFILTER 0 // set to 0 if you want to scan all devices, not filtering smartphone OUIs #define VENDORFILTER 0 // set to 0 if you want to scan all devices, not filtering smartphone OUIs
#define BLECOUNTER 0 // set to 0 if you do not want to install the BLE sniffer #define BLECOUNTER 1 // set to 0 if you do not want to install the BLE sniffer
#define WIFICOUNTER 1 // set to 0 if you do not want to install the WIFI sniffer #define WIFICOUNTER 1 // set to 0 if you do not want to install the WIFI sniffer
#define MAC_QUEUE_SIZE 50 // size of MAC processing buffer (number of MACs) [default = 50] #define MAC_QUEUE_SIZE 50 // size of MAC processing buffer (number of MACs) [default = 50]
@ -26,11 +27,11 @@
#define BLESCANINTERVAL 80 // [illiseconds] scan interval, see below, 3 .. 10240, default 80ms = 100% duty cycle #define BLESCANINTERVAL 80 // [illiseconds] scan interval, see below, 3 .. 10240, default 80ms = 100% duty cycle
// Corona Exposure Notification Service(ENS) counter // Corona Exposure Notification Service(ENS) counter
#define COUNT_ENS 0 // count found number of devices which advertise Exposure Notification Service #define COUNT_ENS 1 // count found number of devices which advertise Exposure Notification Service
// set to 0 if you do not want to enable this function // set to 1 if you want to enable this function [default=0]
// for additional sensors (added by some user) // for additional sensors (added by some user)
#define HAS_SENSOR_1 0 // set to 1 to enable data transfer of user sensor #1 (also used as ENS counter) [default=0] #define HAS_SENSOR_1 1 // set to 1 to enable data transfer of user sensor #1 (also used as ENS counter) [default=0]
#define HAS_SENSOR_2 0 // set to 1 to enable data transfer of user sensor #2 [default=0] #define HAS_SENSOR_2 0 // set to 1 to enable data transfer of user sensor #2 [default=0]
#define HAS_SENSOR_3 0 // set to 1 to enable data transfer of user sensor #3 [default=0] #define HAS_SENSOR_3 0 // set to 1 to enable data transfer of user sensor #3 [default=0]
@ -83,10 +84,10 @@
#define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds] #define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds]
// settings for syncing time of node with a time source (network / gps / rtc / timeserver) // settings for syncing time of node with a time source (network / gps / rtc / timeserver)
#define TIME_SYNC_LORAWAN 1 // set to 1 to use LORA network as time source, 0 means off [default = 1] #define TIME_SYNC_LORAWAN 0 // set to 1 to use LORA network as time source, 0 means off [default = 1]
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0] #define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
#define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source [default = 60], 0 means off #define TIME_SYNC_INTERVAL 60 // sync time attempt each .. minutes from time source [default = 60], 0 means off
#define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off #define TIME_SYNC_INTERVAL_RETRY 10 // retry time sync after lost sync each .. minutes [default = 10], 0 means off
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging, max. 255 #define TIME_SYNC_SAMPLES 1 // number of time requests for averaging, max. 255
#define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds] #define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds]
#define TIME_SYNC_TIMEOUT 400 // timeout waiting for timeserver answer [seconds] #define TIME_SYNC_TIMEOUT 400 // timeout waiting for timeserver answer [seconds]

View File

@ -48,7 +48,7 @@ void AXP192_powerevent_IRQ(void) {
ESP_LOGI(TAG, "Battery low temperature."); ESP_LOGI(TAG, "Battery low temperature.");
// short press -> esp32 deep sleep mode, can be exited by pressing user button // short press -> esp32 deep sleep mode, can be exited by pressing user button
if (pmu.isPEKShortPressIRQ() && (RTC_runmode == RUNMODE_NORMAL)) { if (pmu.isPEKShortPressIRQ()) {
enter_deepsleep(0, HAS_BUTTON); enter_deepsleep(0, HAS_BUTTON);
} }

View File

@ -5,21 +5,27 @@
// Local logging tag // Local logging tag
static const char TAG[] = __FILE__; static const char TAG[] = __FILE__;
// Conversion factor for micro seconds to seconds
#define uS_TO_S_FACTOR 1000000ULL
// variable keep its values after restart or wakeup from sleep // variable keep its values after restart or wakeup from sleep
RTC_NOINIT_ATTR runmode_t RTC_runmode; RTC_NOINIT_ATTR runmode_t RTC_runmode;
const char *runmode[4] = {"powercycle", "normal", "wakeup", "update"};
void do_reset(bool warmstart) { void do_reset(bool warmstart) {
if (warmstart) { if (warmstart) {
// store LMIC keys and counters in RTC memory ESP_LOGI(TAG, "restarting device (warmstart), keeping runmode %s",
ESP_LOGI(TAG, "restarting device (warmstart), keeping runmode %d", runmode[RTC_runmode]);
RTC_runmode);
} else { } else {
#if (HAS_LORA) #if (HAS_LORA)
if (RTC_runmode == RUNMODE_NORMAL) if (RTC_runmode == RUNMODE_NORMAL) {
LMIC_shutdown(); LMIC_shutdown();
}
#endif #endif
RTC_runmode = RUNMODE_POWERCYCLE; RTC_runmode = RUNMODE_POWERCYCLE;
ESP_LOGI(TAG, "restarting device (coldstart), set runmode %d", RTC_runmode); ESP_LOGI(TAG, "restarting device (coldstart), setting runmode %s",
runmode[RTC_runmode]);
} }
esp_restart(); esp_restart();
} }
@ -40,9 +46,6 @@ void do_after_reset(int reason) {
case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core case DEEPSLEEP_RESET: // 0x05 Deep Sleep reset digital core
RTC_runmode = RUNMODE_WAKEUP; RTC_runmode = RUNMODE_WAKEUP;
#if (HAS_LORA)
// to be done: restore LoRaWAN channel configuration and datarate here
#endif
break; break;
case SW_RESET: // 0x03 Software reset digital core case SW_RESET: // 0x03 Software reset digital core
@ -61,32 +64,61 @@ void do_after_reset(int reason) {
break; break;
} }
ESP_LOGI(TAG, "Starting Software v%s, runmode %d", PROGVERSION, RTC_runmode); ESP_LOGI(TAG, "Starting Software v%s, runmode %s", PROGVERSION,
runmode[RTC_runmode]);
} }
void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) { void enter_deepsleep(const int wakeup_sec = 60,
const gpio_num_t wakeup_gpio = GPIO_NUM_MAX) {
if ((!wakeup_sec) && (!wakeup_gpio) && (RTC_runmode == RUNMODE_NORMAL)) // ensure we are in normal runmode, not udpate or wakeup
return; if ((RTC_runmode != RUNMODE_NORMAL)
// wait until LMIC is in safe state before going to sleep
#if (HAS_LORA) #if (HAS_LORA)
while (os_queryTimeCriticalJobs(ms2osticks(wakeup_sec * 1000))) || (LMIC.opmode & (OP_JOINING | OP_REJOIN))
vTaskDelay(pdMS_TO_TICKS(100)); #endif
) {
// to be done: save current LoRaWAN configuration here ESP_LOGE(TAG, "Can't go to sleep now");
return;
} else {
ESP_LOGI(TAG, "Attempting to sleep...");
}
// switch off radio
#if (BLECOUNTER)
stop_BLEscan();
btStop();
#endif
#if (WIFICOUNTER)
switch_wifi_sniffer(0);
#endif #endif
// set up power domains // wait until all send queues are empty
//esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON); ESP_LOGI(TAG, "Waiting until send queues are empty...");
while (!allQueuesEmtpy())
vTaskDelay(pdMS_TO_TICKS(100));
// set wakeup timer #if (HAS_LORA)
if (wakeup_sec) // shutdown LMIC safely
esp_sleep_enable_timer_wakeup(wakeup_sec * 1000000); ESP_LOGI(TAG, "Waiting until LMIC is idle...");
while ((LMIC.opmode & OP_TXRXPEND) ||
os_queryTimeCriticalJobs(sec2osticks(wakeup_sec)))
vTaskDelay(pdMS_TO_TICKS(100));
// set wakeup gpio SaveLMICToRTC(wakeup_sec);
if (wakeup_gpio != NOT_A_PIN) { // vTaskDelete(lmicTask);
// LMIC_shutdown();
#endif // (HAS_LORA)
// set up RTC power domains
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_ON);
// set up RTC wakeup timer, if we have
if (wakeup_sec > 0) {
esp_sleep_enable_timer_wakeup(wakeup_sec * uS_TO_S_FACTOR);
}
// set wakeup gpio, if we have
if (wakeup_gpio != GPIO_NUM_MAX) {
rtc_gpio_isolate(wakeup_gpio); rtc_gpio_isolate(wakeup_gpio);
esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW); esp_sleep_enable_ext1_wakeup(1ULL << wakeup_gpio, ESP_EXT1_WAKEUP_ALL_LOW);
} }
@ -99,15 +131,6 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) {
dp_shutdown(); dp_shutdown();
#endif #endif
// switch off radio
#if (BLECOUNTER)
stop_BLEscan();
btStop();
#endif
#if (WIFICOUNTER)
switch_wifi_sniffer(0);
#endif
// reduce power if has PMU // reduce power if has PMU
#ifdef HAS_PMU #ifdef HAS_PMU
AXP192_power(pmu_power_sleep); AXP192_power(pmu_power_sleep);
@ -117,6 +140,6 @@ void enter_deepsleep(const int wakeup_sec, const gpio_num_t wakeup_gpio) {
i2c_deinit(); i2c_deinit();
// enter sleep mode // enter sleep mode
ESP_LOGI(TAG, "Going to sleep..."); ESP_LOGI(TAG, "Going to sleep, good bye.");
esp_deep_sleep_start(); esp_deep_sleep_start();
} }

View File

@ -10,6 +10,8 @@ void setSendIRQ() {
// 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
void SendPayload(uint8_t port, sendprio_t prio) { void SendPayload(uint8_t port, sendprio_t prio) {
ESP_LOGD(TAG, "sending Payload for Port %d (prio %d)", port, prio);
MessageBuffer_t MessageBuffer_t
SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[] SendBuffer; // contains MessageSize, MessagePort, MessagePrio, Message[]
@ -187,14 +189,9 @@ void sendData() {
bitmask &= ~mask; bitmask &= ~mask;
mask <<= 1; mask <<= 1;
} // while (bitmask) } // while (bitmask)
// goto sleep if we have a sleep cycle
if ((cfg.sleepcycle) && (RTC_runmode == RUNMODE_NORMAL))
enter_deepsleep(cfg.sleepcycle * 2, HAS_BUTTON);
} // sendData() } // sendData()
void flushQueues() { void flushQueues(void) {
#if (HAS_LORA) #if (HAS_LORA)
lora_queuereset(); lora_queuereset();
#endif #endif
@ -205,3 +202,17 @@ void flushQueues() {
mqtt_queuereset(); mqtt_queuereset();
#endif #endif
} }
bool allQueuesEmtpy(void) {
uint32_t rc = 0;
#if (HAS_LORA)
rc += lora_queuewaiting();
#endif
#ifdef HAS_SPI
rc += spi_queuewaiting();
#endif
#ifdef HAS_MQTT
rc += mqtt_queuewaiting();
#endif
return (rc == 0) ? true : false;
}

View File

@ -57,7 +57,8 @@ void spi_slave_task(void *param) {
memset(rxbuf, 0, sizeof(rxbuf)); memset(rxbuf, 0, sizeof(rxbuf));
// fetch next or wait for payload to send from queue // fetch next or wait for payload to send from queue
if (xQueueReceive(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) { // do not delete item from queue until it is transmitted
if (xQueuePeek(SPISendQueue, &msg, portMAX_DELAY) != pdTRUE) {
ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!"); ESP_LOGE(TAG, "Premature return from xQueueReceive() with no data!");
continue; continue;
} }
@ -96,6 +97,9 @@ void spi_slave_task(void *param) {
ESP_LOGI(TAG, "Transaction finished with size %zu bits", ESP_LOGI(TAG, "Transaction finished with size %zu bits",
spi_transaction.trans_len); spi_transaction.trans_len);
// delete sent item from queue
xQueueReceive(SPISendQueue, &msg, (TickType_t)0);
// check if command was received, then call interpreter with command payload // check if command was received, then call interpreter with command payload
if ((spi_transaction.trans_len) && ((rxbuf[2]) == RCMDPORT)) { if ((spi_transaction.trans_len) && ((rxbuf[2]) == RCMDPORT)) {
rcommand(rxbuf + HEADER_SIZE, spi_transaction.trans_len - HEADER_SIZE); rcommand(rxbuf + HEADER_SIZE, spi_transaction.trans_len - HEADER_SIZE);
@ -159,11 +163,11 @@ void spi_enqueuedata(MessageBuffer_t *message) {
if (!uxQueueSpacesAvailable(SPISendQueue)) if (!uxQueueSpacesAvailable(SPISendQueue))
xQueueReceive(SPISendQueue, &DummyBuffer, (TickType_t)0); xQueueReceive(SPISendQueue, &DummyBuffer, (TickType_t)0);
case prio_normal: case prio_normal:
ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0); ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0);
break; break;
case prio_low: case prio_low:
default: default:
ret = xQueueSendToBack(SPISendQueue, (void *)message, (TickType_t)0); ret = xQueueSendToFront(SPISendQueue, (void *)message, (TickType_t)0);
break; break;
} }
if (ret != pdTRUE) if (ret != pdTRUE)
@ -172,4 +176,6 @@ void spi_enqueuedata(MessageBuffer_t *message) {
void spi_queuereset(void) { xQueueReset(SPISendQueue); } void spi_queuereset(void) { xQueueReset(SPISendQueue); }
uint32_t spi_queuewaiting(void) { return uxQueueMessagesWaiting(SPISendQueue); }
#endif // HAS_SPI #endif // HAS_SPI