#define IS_VALID_DETECT 0xa00ab00bc00bd00d; #include "Arduino.h" #include "hardware/W5500EthServer.h" #include "hardware/WifiEthServer.h" #include "esp_crt_bundle.h" #include "esp_ota_ops.h" #include "esp_http_client.h" #include "esp_https_ota.h" #ifndef NUKI_HUB_UPDATER #include "Config.h" #include "NukiWrapper.h" #include "NukiNetworkLock.h" #include "PresenceDetection.h" #include "NukiOpenerWrapper.h" #include "Gpio.h" #include "CharBuffer.h" #include "NukiDeviceId.h" #include "WebCfgServer.h" #include "Logger.h" #include "PreferencesKeys.h" #include "RestartReason.h" NukiNetworkLock* networkLock = nullptr; NukiNetworkOpener* networkOpener = nullptr; BleScanner::Scanner* bleScanner = nullptr; NukiWrapper* nuki = nullptr; NukiOpenerWrapper* nukiOpener = nullptr; PresenceDetection* presenceDetection = nullptr; NukiDeviceId* deviceIdLock = nullptr; NukiDeviceId* deviceIdOpener = nullptr; Gpio* gpio = nullptr; bool lockEnabled = false; bool openerEnabled = false; TaskHandle_t nukiTaskHandle = nullptr; TaskHandle_t presenceDetectionTaskHandle = nullptr; int64_t restartTs = ((2^64) - (5 * 1000 * 60000)) / 1000; #else #include "../../src/WebCfgServer.h" #include "../../src/Logger.h" #include "../../src/PreferencesKeys.h" #include "../../src/Config.h" #include "../../src/RestartReason.h" #include "../../src/NukiNetwork.h" int64_t restartTs = 10 * 1000 * 60000; #endif NukiNetwork* network = nullptr; WebCfgServer* webCfgServer = nullptr; Preferences* preferences = nullptr; EthServer* ethServer = nullptr; RTC_NOINIT_ATTR int restartReason; RTC_NOINIT_ATTR uint64_t restartReasonValidDetect; RTC_NOINIT_ATTR bool rebuildGpioRequested; RTC_NOINIT_ATTR uint64_t bootloopValidDetect; RTC_NOINIT_ATTR int8_t bootloopCounter; bool restartReason_isValid; RestartReason currentRestartReason = RestartReason::NotApplicable; TaskHandle_t otaTaskHandle = nullptr; TaskHandle_t networkTaskHandle = nullptr; void networkTask(void *pvParameters) { while(true) { int64_t ts = (esp_timer_get_time() / 1000); if(ts > 120000 && ts < 125000 && bootloopCounter > 0) { bootloopCounter = (int8_t)0; Log->println(F("Bootloop counter reset")); } bool connected = network->update(); #ifndef NUKI_HUB_UPDATER if(connected && openerEnabled) { networkOpener->update(); } if(preferences->getBool(preference_webserver_enabled, true)) { webCfgServer->update(); } #else webCfgServer->update(); #endif delay(100); } } #ifndef NUKI_HUB_UPDATER void nukiTask(void *pvParameters) { while(true) { bleScanner->update(); delay(20); bool needsPairing = (lockEnabled && !nuki->isPaired()) || (openerEnabled && !nukiOpener->isPaired()); if (needsPairing) { delay(5000); } if(lockEnabled) { nuki->update(); } if(openerEnabled) { nukiOpener->update(); } } } void bootloopDetection() { uint64_t cmp = IS_VALID_DETECT; bool bootloopIsValid = (bootloopValidDetect == cmp); Log->println(bootloopIsValid); if(!bootloopIsValid) { bootloopCounter = (int8_t)0; bootloopValidDetect = IS_VALID_DETECT; return; } if(esp_reset_reason() == esp_reset_reason_t::ESP_RST_PANIC || esp_reset_reason() == esp_reset_reason_t::ESP_RST_INT_WDT || esp_reset_reason() == esp_reset_reason_t::ESP_RST_TASK_WDT || true || esp_reset_reason() == esp_reset_reason_t::ESP_RST_WDT) { bootloopCounter++; Log->print(F("Bootloop counter incremented: ")); Log->println(bootloopCounter); if(bootloopCounter == 10) { Log->print(F("Bootloop detected.")); preferences->putInt(preference_buffer_size, CHAR_BUFFER_SIZE); preferences->putInt(preference_task_size_network, NETWORK_TASK_SIZE); preferences->putInt(preference_task_size_nuki, NUKI_TASK_SIZE); preferences->putInt(preference_authlog_max_entries, MAX_AUTHLOG); preferences->putInt(preference_keypad_max_entries, MAX_KEYPAD); preferences->putInt(preference_timecontrol_max_entries, MAX_TIMECONTROL); bootloopCounter = 0; } } } #endif uint8_t checkPartition() { const esp_partition_t* running_partition = esp_ota_get_running_partition(); Log->print(F("Partition size: ")); Log->println(running_partition->size); Log->print(F("Partition subtype: ")); Log->println(running_partition->subtype); if(running_partition->size == 1966080) return 0; //OLD PARTITION TABLE else if(running_partition->subtype == ESP_PARTITION_SUBTYPE_APP_OTA_0) return 1; //NEW PARTITION TABLE, RUNNING MAIN APP else return 2; //NEW PARTITION TABLE, RUNNING UPDATER APP } esp_err_t _http_event_handler(esp_http_client_event_t *evt) { switch (evt->event_id) { case HTTP_EVENT_ERROR: Log->println("HTTP_EVENT_ERROR"); break; case HTTP_EVENT_ON_CONNECTED: Log->println("HTTP_EVENT_ON_CONNECTED"); break; case HTTP_EVENT_HEADER_SENT: Log->println("HTTP_EVENT_HEADER_SENT"); break; case HTTP_EVENT_ON_HEADER: Log->println("HTTP_EVENT_ON_HEADER"); break; case HTTP_EVENT_ON_DATA: Log->println("HTTP_EVENT_ON_DATA"); break; case HTTP_EVENT_ON_FINISH: Log->println("HTTP_EVENT_ON_FINISH"); break; case HTTP_EVENT_DISCONNECTED: Log->println("HTTP_EVENT_DISCONNECTED"); break; #if (ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 0, 0)) case HTTP_EVENT_REDIRECT: Log->println("HTTP_EVENT_REDIRECT"); break; #endif } return ESP_OK; } #if (ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 0, 0)) void otaTask(void *pvParameter) { uint8_t partitionType = checkPartition(); String updateUrl; if(partitionType==1) { updateUrl = preferences->getString(preference_ota_updater_url); preferences->putString(preference_ota_updater_url, ""); } else { updateUrl = preferences->getString(preference_ota_main_url); preferences->putString(preference_ota_main_url, ""); } Log->println("Starting OTA task"); esp_http_client_config_t config = { .url = updateUrl.c_str(), .event_handler = _http_event_handler, .crt_bundle_attach = esp_crt_bundle_attach, .keep_alive_enable = true, }; esp_https_ota_config_t ota_config = { .http_config = &config, }; Log->print(F("Attempting to download update from ")); Log->println(config.url); esp_err_t ret = esp_https_ota(&ota_config); if (ret == ESP_OK) { Log->println("OTA Succeeded, Rebooting..."); esp_ota_set_boot_partition(esp_ota_get_next_update_partition(NULL)); restartEsp(RestartReason::OTACompleted); } else { Log->println("Firmware upgrade failed"); restartEsp(RestartReason::OTAAborted); } while (1) { vTaskDelay(1000 / portTICK_PERIOD_MS); } } #endif void setupTasks(bool ota) { // configMAX_PRIORITIES is 25 if(ota) { #if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)) xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, 1); #ifndef NUKI_HUB_UPDATER xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 1); #endif #else xTaskCreatePinnedToCore(otaTask, "ota", 8192, NULL, 2, &otaTaskHandle, 1); #endif } else { xTaskCreatePinnedToCore(networkTask, "ntw", preferences->getInt(preference_task_size_network, NETWORK_TASK_SIZE), NULL, 3, &networkTaskHandle, 1); #ifndef NUKI_HUB_UPDATER xTaskCreatePinnedToCore(nukiTask, "nuki", preferences->getInt(preference_task_size_nuki, NUKI_TASK_SIZE), NULL, 2, &nukiTaskHandle, 1); #endif } } void initEthServer(const NetworkDeviceType device) { switch (device) { case NetworkDeviceType::W5500: ethServer = new W5500EthServer(80); break; case NetworkDeviceType::WiFi: ethServer = new WifiEthServer(80); break; default: ethServer = new WifiEthServer(80); break; } } void setup() { Serial.begin(115200); Log = &Serial; preferences = new Preferences(); preferences->begin("nukihub", false); bool firstStart = initPreferences(preferences); uint8_t partitionType = checkPartition(); initializeRestartReason(); #ifndef NUKI_HUB_UPDATER if(preferences->getBool(preference_enable_bootloop_reset, false)) { bootloopDetection(); } #endif #ifdef NUKI_HUB_UPDATER Log->print(F("Nuki Hub OTA version ")); Log->println(NUKI_HUB_VERSION); Log->print(F("Nuki Hub OTA build ")); Log->println(NUKI_HUB_BUILD); network = new NukiNetwork(preferences); network->initialize(); initEthServer(network->networkDeviceType()); webCfgServer = new WebCfgServer(network, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType); webCfgServer->initialize(); #else Log->print(F("Nuki Hub version ")); Log->println(NUKI_HUB_VERSION); Log->print(F("Nuki Hub build ")); Log->println(NUKI_HUB_BUILD); uint32_t devIdOpener = preferences->getUInt(preference_device_id_opener); deviceIdLock = new NukiDeviceId(preferences, preference_device_id_lock); deviceIdOpener = new NukiDeviceId(preferences, preference_device_id_opener); if(deviceIdLock->get() != 0 && devIdOpener == 0) { deviceIdOpener->assignId(deviceIdLock->get()); } char16_t buffer_size = preferences->getInt(preference_buffer_size, 4096); CharBuffer::initialize(buffer_size); gpio = new Gpio(preferences); String gpioDesc; gpio->getConfigurationText(gpioDesc, gpio->pinConfiguration(), "\n\r"); Serial.print(gpioDesc.c_str()); bleScanner = new BleScanner::Scanner(); // Scan interval and window according to Nuki recommendations: // https://developer.nuki.io/t/bluetooth-specification-questions/1109/27 bleScanner->initialize("NukiHub", true, 40, 40); bleScanner->setScanDuration(10); #if PRESENCE_DETECTION_ENABLED if(preferences->getInt(preference_presence_detection_timeout) >= 0) { presenceDetection = new PresenceDetection(preferences, bleScanner, CharBuffer::get(), buffer_size); presenceDetection->initialize(); } #endif lockEnabled = preferences->getBool(preference_lock_enabled); openerEnabled = preferences->getBool(preference_opener_enabled); const String mqttLockPath = preferences->getString(preference_mqtt_lock_path); network = new NukiNetwork(preferences, presenceDetection, gpio, mqttLockPath, CharBuffer::get(), buffer_size); network->initialize(); networkLock = new NukiNetworkLock(network, preferences, CharBuffer::get(), buffer_size); networkLock->initialize(); if(openerEnabled) { networkOpener = new NukiNetworkOpener(network, preferences, CharBuffer::get(), buffer_size); networkOpener->initialize(); } initEthServer(network->networkDeviceType()); Log->println(lockEnabled ? F("Nuki Lock enabled") : F("Nuki Lock disabled")); if(lockEnabled) { nuki = new NukiWrapper("NukiHub", deviceIdLock, bleScanner, networkLock, gpio, preferences); nuki->initialize(firstStart); } Log->println(openerEnabled ? F("Nuki Opener enabled") : F("Nuki Opener disabled")); if(openerEnabled) { nukiOpener = new NukiOpenerWrapper("NukiHub", deviceIdOpener, bleScanner, networkOpener, gpio, preferences); nukiOpener->initialize(); } if(preferences->getBool(preference_webserver_enabled, true)) { webCfgServer = new WebCfgServer(nuki, nukiOpener, network, gpio, ethServer, preferences, network->networkDeviceType() == NetworkDeviceType::WiFi, partitionType); webCfgServer->initialize(); } #endif if((partitionType==1 && preferences->getString(preference_ota_updater_url).length() > 0) || (partitionType==2 && preferences->getString(preference_ota_main_url).length() > 0)) setupTasks(true); else setupTasks(false); } void loop() { vTaskDelete(NULL); } void printBeforeSetupInfo() { } void printAfterSetupInfo() { }