diff --git a/platformio.ini b/platformio.ini index f7f55f18..518aaf2b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,7 +26,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.5.2 +release_version = 1.5.3 ; 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 = 0 diff --git a/src/cyclic.cpp b/src/cyclic.cpp index 95185a98..b0c1a0b5 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -4,7 +4,7 @@ // Basic config #include "globals.h" #include "senddata.h" -#include "OTA.h" +#include "ota.h" // Local logging tag static const char TAG[] = "main"; diff --git a/src/main.cpp b/src/main.cpp index f7bbcbf1..8f4bd99c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,6 +21,25 @@ NOTICE: Parts of the source files in this repository are made available under different licenses. Refer to LICENSE.txt file in repository for more details. +//////////////////////// ESP32-Paxcounter \\\\\\\\\\\\\\\\\\\\\\\\\\ + +Uused tasks and timers: + +Task Core Prio Purpose +==================================================================== +IDLE 0 0 ESP32 arduino scheduler +gpsloop 0 2 read data from GPS over serial or i2c +IDLE 1 0 Arduino loop() -> used for LED switching +loraloop 1 1 runs the LMIC stack +statemachine 1 3 switches application process logic + +ESP32 hardware timers +========================== + 0 Display-Refresh + 1 Wifi Channel Switch + 2 Send Cycle + 3 Housekeeping + */ // Basic Config @@ -90,39 +109,66 @@ void setup() { esp_log_set_vprintf(redirect_log); #endif - ESP_LOGI(TAG, "Starting %s v%s", PRODUCTNAME, PROGVERSION); - - // initialize system event handler for wifi task, needed for - // wifi_sniffer_init() - // esp_event_loop_init(NULL, NULL); - // ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); - - // print chip information on startup if in verbose mode -#ifdef VERBOSE - 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, "ESP32 SDK: %s", ESP.getSdkVersion()); - ESP_LOGI(TAG, "Free RAM: %d bytes", ESP.getFreeHeap()); - -#ifdef HAS_GPS - ESP_LOGI(TAG, "TinyGPS+ v%s", TinyGPSPlus::libraryVersion()); -#endif - -#endif // verbose - - // read settings from NVRAM + // read (and initialize on first run) runtime settings from NVRAM loadConfig(); // includes initialize if necessary -#ifdef VENDORFILTER - strcat_P(features, " OUIFLT"); + // initialize leds +#if (HAS_LED != NOT_A_PIN) + pinMode(HAS_LED, OUTPUT); + strcat_P(features, " LED"); +#endif +#ifdef HAS_RGB_LED + rgb_set_color(COLOR_PINK); + strcat_P(features, " RGB"); +#endif + + // initialize wifi antenna +#ifdef HAS_ANTENNA_SWITCH + strcat_P(features, " ANT"); + antenna_init(); + antenna_select(cfg.wifiant); +#endif + +// switch off bluetooth, if not compiled +#ifdef BLECOUNTER + strcat_P(features, " BLE"); +#else + bool btstop = btStop(); +#endif + +// initialize battery status +#ifdef HAS_BATTERY_PROBE + strcat_P(features, " BATT"); + calibrate_voltage(); + batt_voltage = read_voltage(); +#endif + + // reboot to firmware update mode if ota trigger switch is set + if (cfg.runmode == 1) { + cfg.runmode = 0; + saveConfig(); + start_ota_update(); + } + + // initialize button +#ifdef HAS_BUTTON + strcat_P(features, " BTN_"); +#ifdef BUTTON_PULLUP + strcat_P(features, "PU"); + // install button interrupt (pullup mode) + pinMode(HAS_BUTTON, INPUT_PULLUP); + attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING); +#else + strcat_P(features, "PD"); + // install button interrupt (pulldown mode) + pinMode(HAS_BUTTON, INPUT_PULLDOWN); + attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, FALLING); +#endif // BUTTON_PULLUP +#endif // HAS_BUTTON + +// initialize gps +#ifdef HAS_GPS + strcat_P(features, " GPS"); #endif // initialize LoRa @@ -149,58 +195,32 @@ void setup() { SEND_QUEUE_SIZE * PAYLOAD_BUFFER_SIZE); #endif - // initialize led -#if (HAS_LED != NOT_A_PIN) - pinMode(HAS_LED, OUTPUT); - strcat_P(features, " LED"); +#ifdef VENDORFILTER + strcat_P(features, " OUIFLT"); #endif -#ifdef HAS_RGB_LED - rgb_set_color(COLOR_PINK); - strcat_P(features, " RGB"); -#endif + ESP_LOGI(TAG, "Starting %s v%s", PRODUCTNAME, PROGVERSION); - // initialize button -#ifdef HAS_BUTTON - strcat_P(features, " BTN_"); -#ifdef BUTTON_PULLUP - strcat_P(features, "PU"); - // install button interrupt (pullup mode) - pinMode(HAS_BUTTON, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, RISING); -#else - strcat_P(features, "PD"); - // install button interrupt (pulldown mode) - pinMode(HAS_BUTTON, INPUT_PULLDOWN); - attachInterrupt(digitalPinToInterrupt(HAS_BUTTON), ButtonIRQ, FALLING); -#endif // BUTTON_PULLUP -#endif // HAS_BUTTON + // print chip information on startup if in verbose mode +#ifdef VERBOSE + 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, "ESP32 SDK: %s", ESP.getSdkVersion()); + ESP_LOGI(TAG, "Free RAM: %d bytes", ESP.getFreeHeap()); - // initialize wifi antenna -#ifdef HAS_ANTENNA_SWITCH - strcat_P(features, " ANT"); - antenna_init(); - antenna_select(cfg.wifiant); -#endif - -// switch off bluetooth on esp32 module, if not compiled -#ifdef BLECOUNTER - strcat_P(features, " BLE"); -#else - bool btstop = btStop(); -#endif - -// initialize gps #ifdef HAS_GPS - strcat_P(features, " GPS"); + ESP_LOGI(TAG, "TinyGPS+ v%s", TinyGPSPlus::libraryVersion()); #endif -// initialize battery status -#ifdef HAS_BATTERY_PROBE - strcat_P(features, " BATT"); - calibrate_voltage(); - batt_voltage = read_voltage(); -#endif +#endif // verbose // initialize display #ifdef HAS_DISPLAY @@ -208,16 +228,6 @@ void setup() { DisplayState = cfg.screenon; init_display(PRODUCTNAME, PROGVERSION); - /* - Usage of ESP32 hardware timers - ============================== - - 0 Display-Refresh - 1 Wifi Channel Switch - 2 Send Cycle - 3 Housekeeping - */ - // setup display refresh trigger IRQ using esp32 hardware timer // https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/ @@ -232,13 +242,6 @@ void setup() { timerAlarmEnable(displaytimer); #endif - // reboot to firmware update mode if ota trigger switch is set - if (cfg.runmode == 1) { - cfg.runmode = 0; - saveConfig(); - start_ota_update(); - } - // setup channel rotation trigger IRQ using esp32 hardware timer 1 channelSwitch = timerBegin(1, 800, true); timerAttachInterrupt(channelSwitch, &ChannelSwitchIRQ, true); @@ -293,18 +296,6 @@ void setup() { // join network LMIC_startJoining(); - /* - - Task Core Prio Purpose - ==================================================================== - IDLE 0 0 ESP32 arduino scheduler - gpsloop 0 2 read data from GPS over serial or i2c - IDLE 1 0 Arduino loop() -> used for LED switching - loraloop 1 1 runs the LMIC stack - statemachine 1 3 switches application process logic - - */ - // start lmic runloop in rtos task on core 1 // (note: arduino main loop runs on core 1, too) // https://techtutorialsx.com/2017/05/09/esp32-get-task-execution-core/ @@ -332,6 +323,8 @@ void setup() { // start wifi in monitor mode and start channel rotation task on core 0 ESP_LOGI(TAG, "Starting Wifi..."); + // esp_event_loop_init(NULL, NULL); + // ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); wifi_sniffer_init(); // initialize salt value using esp_random() called by random() in // arduino-esp32 core. Note: do this *after* wifi has started, since @@ -345,40 +338,9 @@ void setup() { } // setup() -void stateMachine(void *pvParameters) { - - configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check - - while (1) { - -#ifdef HAS_BUTTON - readButton(); -#endif - -#ifdef HAS_DISPLAY - updateDisplay(); -#endif - - // check wifi scan cycle and if due rotate channel - if (ChannelTimerIRQ) - switchWifiChannel(channel); - // check housekeeping cycle and if due do the work - if (HomeCycleIRQ) - doHousekeeping(); - // check send queue and process it - enqueuePayload(); - // check send cycle and if due enqueue payload to send - if (SendCycleTimerIRQ) - sendPayload(); - - // give yield to CPU - vTaskDelay(2 / portTICK_PERIOD_MS); - } -} - void loop() { -// switch LED states if device has a LED +// switch LED state if device has LED(s) #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) led_loop(); #endif diff --git a/src/main.h b/src/main.h index e1d8b775..40e9c208 100644 --- a/src/main.h +++ b/src/main.h @@ -1,20 +1,17 @@ #ifndef _MAIN_H #define _MAIN_H -#include "globals.h" -#include "led.h" -#include "macsniff.h" -#include "wifiscan.h" -#include "configmanager.h" -#include "senddata.h" -#include "cyclic.h" -#include "beacon_array.h" -#include "OTA.h" - #include // needed for reading ESP32 chip attributes #include // needed for Wifi event handler #include // needed for timers -void stateMachine(void *pvParameters); +#include "globals.h" +#include "led.h" +#include "wifiscan.h" +#include "configmanager.h" +#include "cyclic.h" +#include "beacon_array.h" +#include "ota.h" +#include "statemachine.h" #endif \ No newline at end of file diff --git a/src/ota.cpp b/src/ota.cpp index 863b61c7..c4f64c90 100644 --- a/src/ota.cpp +++ b/src/ota.cpp @@ -35,15 +35,36 @@ volatile bool isValidContentType = false; // Local logging tag static const char TAG[] = "main"; -void display(const uint8_t x, const uint8_t y, char* text) { +void display(const uint8_t row, std::string status, std::string msg) { #ifdef HAS_DISPLAY - u8x8.setCursor(x, y); - u8x8.print(text); + u8x8.setCursor(14, row); + u8x8.print((status.substr(0, 2)).c_str()); + if (!msg.empty()) { + u8x8.clearLine(7); + u8x8.setCursor(0, 7); + u8x8.print(msg.substr(0, 16).c_str()); + } #endif } +// callback function to show download progress while streaming data +void show_progress(size_t current, size_t size) { + char buf[17]; + snprintf(buf, 17, "%-9lu (%3lu%%)", current, current*100 / size); + display(4, "**", buf); +} + void start_ota_update() { +// turn on LED +#if (HAS_LED != NOT_A_PIN) +#ifdef LED_ACTIVE_LOW + digitalWrite(HAS_LED, LOW); +#else + digitalWrite(HAS_LED, HIGH); +#endif +#endif + #ifdef HAS_DISPLAY u8x8.begin(); u8x8.setFont(u8x8_font_chroma48medium8_r); @@ -51,67 +72,80 @@ void start_ota_update() { #ifdef DISPLAY_FLIP u8x8.setFlipMode(1); #endif - u8x8.draw2x2String(0, 0, "UPDATING"); - u8x8.setCursor(0, 3); - u8x8.print("Wifi connect ..\n"); - u8x8.print("Get Update? ..\n"); + u8x8.setInverseFont(1); + u8x8.print("SOFTWARE UPDATE \n"); + u8x8.setInverseFont(0); + u8x8.print("WiFi connect ..\n"); + u8x8.print("Has Update? ..\n"); u8x8.print("Downloading ..\n"); u8x8.print("Flashing ..\n"); u8x8.print("Rebooting .."); #endif ESP_LOGI(TAG, "Starting Wifi OTA update"); - display(14, 3, "**"); + display(1, "**", WIFI_SSID); WiFi.begin(WIFI_SSID, WIFI_PASS); int i = WIFI_MAX_TRY; + while (i--) { - ESP_LOGI(TAG, "trying to connect to %s", WIFI_SSID); + ESP_LOGI(TAG, "Trying to connect to %s", WIFI_SSID); if (WiFi.status() == WL_CONNECTED) break; vTaskDelay(5000 / portTICK_PERIOD_MS); } + if (i >= 0) { - ESP_LOGI(TAG, "connected to %s", WIFI_SSID); - display(14, 3, "OK"); - checkFirmwareUpdates(); // gets and flashes new firmware and restarts + ESP_LOGI(TAG, "Connected to %s", WIFI_SSID); + display(1, "OK", "WiFi connected"); + checkFirmwareUpdates(); // gets and flashes new firmware } else { - ESP_LOGI(TAG, "could not connect to %s, rebooting.", WIFI_SSID); - display(14, 3, " E"); + ESP_LOGI(TAG, "Could not connect to %s, rebooting.", WIFI_SSID); + display(1, " E", "no WiFi connect"); } - display(14, 7, "**"); - delay(5000); - ESP.restart(); // reached only if update was not successful or no wifi connect + display(5, "**", ""); // mark line rebooting + +// turn off LED +#if (HAS_LED != NOT_A_PIN) +#ifdef LED_ACTIVE_LOW + digitalWrite(HAS_LED, HIGH); +#else + digitalWrite(HAS_LED, LOW); +#endif +#endif + + vTaskDelay(5000 / portTICK_PERIOD_MS); + ESP.restart(); } // start_ota_update void checkFirmwareUpdates() { // Fetch the latest firmware version - ESP_LOGI(TAG, "OTA mode, checking latest firmware version on server..."); - display(14, 4, "**"); + ESP_LOGI(TAG, "Checking latest firmware version on server..."); + display(2, "**", "checking version"); const String latest = bintray.getLatestVersion(); if (latest.length() == 0) { ESP_LOGI( TAG, "Could not load info about the latest firmware. Rebooting to runmode."); - display(14, 4, " E"); + display(2, " E", "file not found"); return; } else if (version_compare(latest, cfg.version) <= 0) { ESP_LOGI(TAG, "Current firmware is up to date. Rebooting to runmode."); - display(14, 4, "NO"); + display(2, "NO", "no update found"); return; } ESP_LOGI(TAG, "New firmware version v%s available. Downloading...", latest.c_str()); - display(14, 4, "OK"); + display(2, "OK", ""); processOTAUpdate(latest); } -// A helper function to extract header value from header +// helper function to extract header value from header inline String getHeaderValue(String header, String headerName) { return header.substring(strlen(headerName.c_str())); } @@ -120,11 +154,13 @@ inline String getHeaderValue(String header, String headerName) { * OTA update processing */ void processOTAUpdate(const String &version) { - display(14, 5, "**"); + + char buf[17]; + display(3, "**", "requesting file"); String firmwarePath = bintray.getBinaryPath(version); if (!firmwarePath.endsWith(".bin")) { ESP_LOGI(TAG, "Unsupported binary format, OTA update cancelled."); - display(14, 5, " E"); + display(3, " E", "file type error"); return; } @@ -136,7 +172,7 @@ void processOTAUpdate(const String &version) { if (!client.connect(currentHost.c_str(), port)) { ESP_LOGI(TAG, "Cannot connect to %s", currentHost.c_str()); - display(14, 5, " E"); + display(3, " E", "connection lost"); return; } @@ -148,7 +184,7 @@ void processOTAUpdate(const String &version) { if (!client.connect(currentHost.c_str(), port)) { ESP_LOGI(TAG, "Redirect detected, but cannot connect to %s", currentHost.c_str()); - display(14, 5, " E"); + display(3, " E", "server error"); return; } } @@ -164,7 +200,7 @@ void processOTAUpdate(const String &version) { while (client.available() == 0) { if (millis() - timeout > RESPONSE_TIMEOUT_MS) { ESP_LOGI(TAG, "Client Timeout."); - display(14, 5, " E"); + display(3, " E", "client timeout"); client.stop(); return; } @@ -224,35 +260,38 @@ void processOTAUpdate(const String &version) { } } - display(14, 5, "OK"); + display(3, "OK", ""); // line download // check whether we have everything for OTA update if (contentLength && isValidContentType) { - size_t written; + size_t written, current, size; if (Update.begin(contentLength)) { + // register callback function for showing progress while streaming data + Update.onProgress(&show_progress); + int i = FLASH_MAX_TRY; while ((i--) && (written != contentLength)) { ESP_LOGI(TAG, - "Starting OTA update, attempt %d of %d. This will take some " + "Starting OTA update, attempt %u of %u. This will take some " "time to complete...", FLASH_MAX_TRY - i, FLASH_MAX_TRY); - display(14, 6, "**"); + display(4, "**", "writing..."); written = Update.writeStream(client); if (written == contentLength) { - ESP_LOGI(TAG, "Written %d bytes successfully", written); - display(14, 6, "**"); + ESP_LOGI(TAG, "Written %u bytes successfully", written); + snprintf(buf, 17, "%u kB Done!", (uint16_t)(written / 1024)); + display(4, "OK", buf); break; } else { ESP_LOGI(TAG, - "Written only %d of %d bytes, OTA update attempt cancelled.", + "Written only %u of %u bytes, OTA update attempt cancelled.", written, contentLength); - display(14, 6, " E"); } } @@ -262,33 +301,31 @@ void processOTAUpdate(const String &version) { ESP_LOGI( TAG, "OTA update completed. Rebooting to runmode with new version."); - display(14, 7, "OK"); client.stop(); return; } else { ESP_LOGI(TAG, "Something went wrong! OTA update hasn't been finished " "properly."); - display(14, 7, " E"); } } else { ESP_LOGI(TAG, "An error occurred. Error #: %d", Update.getError()); - display(14, 7, " E"); + snprintf(buf, 17, "Error #: %d", Update.getError()); + display(4, " E", buf); } } else { ESP_LOGI(TAG, "There isn't enough space to start OTA update"); - display(14, 7, " E"); + display(4, " E", "disk full"); client.flush(); } } else { ESP_LOGI(TAG, "There was no valid content in the response from the OTA server!"); - display(14, 7, " E"); + display(4, " E", "response error"); client.flush(); } ESP_LOGI(TAG, "OTA update failed. Rebooting to runmode with current version."); - display(14, 7, " E"); client.stop(); } diff --git a/src/ota.h b/src/ota.h index c2eb69c9..25ccbb11 100644 --- a/src/ota.h +++ b/src/ota.h @@ -13,5 +13,6 @@ void checkFirmwareUpdates(); void processOTAUpdate(const String &version); void start_ota_update(); int version_compare(const String v1, const String v2); +void show_progress(size_t current, size_t size); #endif // OTA_H diff --git a/src/senddata.cpp b/src/senddata.cpp index ad647233..f738ee6b 100644 --- a/src/senddata.cpp +++ b/src/senddata.cpp @@ -73,8 +73,8 @@ void IRAM_ATTR SendCycleIRQ() { portEXIT_CRITICAL(&timerMux); } -// interrupt triggered function to eat data from RTos send queues and transmit it -void enqueuePayload() { +// interrupt triggered function to eat data from send queues and transmit it +void checkSendQueues() { MessageBuffer_t SendBuffer; #ifdef HAS_LORA @@ -98,7 +98,7 @@ void enqueuePayload() { } #endif -} // enqueuePayload +} // checkSendQueues void flushQueues() { #ifdef HAS_LORA diff --git a/src/senddata.h b/src/senddata.h index eba9d8bd..df7b53f5 100644 --- a/src/senddata.h +++ b/src/senddata.h @@ -4,7 +4,7 @@ void SendData(uint8_t port); void sendPayload(void); void SendCycleIRQ(void); -void enqueuePayload(void); +void checkSendQueues(void); void flushQueues(); #endif // _SENDDATA_H_ \ No newline at end of file diff --git a/src/statemachine.cpp b/src/statemachine.cpp new file mode 100644 index 00000000..9dfac811 --- /dev/null +++ b/src/statemachine.cpp @@ -0,0 +1,35 @@ +#include "statemachine.h" + +// Local logging tag +static const char TAG[] = "main"; + +void stateMachine(void *pvParameters) { + + configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check + + while (1) { + +#ifdef HAS_BUTTON + readButton(); +#endif + +#ifdef HAS_DISPLAY + updateDisplay(); +#endif + + // check wifi scan cycle and if due rotate channel + if (ChannelTimerIRQ) + switchWifiChannel(channel); + // check housekeeping cycle and if due do the work + if (HomeCycleIRQ) + doHousekeeping(); + // check send cycle and if due enqueue payload to send + if (SendCycleTimerIRQ) + sendPayload(); + // check send queues and process due payload to send + checkSendQueues(); + + // give yield to CPU + vTaskDelay(2 / portTICK_PERIOD_MS); + } +} \ No newline at end of file diff --git a/src/statemachine.h b/src/statemachine.h new file mode 100644 index 00000000..7390e5c9 --- /dev/null +++ b/src/statemachine.h @@ -0,0 +1,12 @@ +#ifndef _STATEMACHINE_H +#define _STATEMACHINE_H + +#include "globals.h" +#include "led.h" +#include "wifiscan.h" +#include "senddata.h" +#include "cyclic.h" + +void stateMachine(void *pvParameters); + +#endif diff --git a/src/update.cpp b/src/update.cpp index f69603cb..a43a9809 100644 --- a/src/update.cpp +++ b/src/update.cpp @@ -310,7 +310,7 @@ size_t UpdateClass::write(uint8_t *data, size_t len) { } size_t UpdateClass::writeStream(Stream &data) { - data.setTimeout(10000); + data.setTimeout(20000); size_t written = 0; size_t toRead = 0; if(hasError() || !isRunning())