diff --git a/.gitignore b/.gitignore index 030b6f3d..f1f6d754 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ .clang_complete .gcc-flags.json src/loraconf.h +platformio.ini \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 45f6672b..121cca95 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,6 +9,7 @@ ; http://docs.platformio.org/page/projectconf.html + ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] ;env_default = generic @@ -29,20 +30,21 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [bintray] user = cyberman54 -repository = paxcounter -package = esp32-paxcounter -api_token = *** +repository = paxcounter-firmware +package = ttgov21_old +api_token = 2e10f923df5d47b9c7e25752510322a1d65ee997 [wifi] -ssid = *** -password = *** +ssid = testnet +password = test0815 [common] platform = https://github.com/platformio/platform-espressif32.git ; firmware version, please modify it between releases ; positive integer value -release_version = 4 +;release_version = 1.4.30 +release_version = 3 ; build configuration based on Bintray and Wi-Fi settings build_flags = @@ -154,6 +156,8 @@ lib_deps = build_flags = ${common.build_flags} ${common_env_data.build_flags} +;upload_protocol = custom +;extra_scripts = pre:publish_firmware.py [env:ttgobeam] platform = ${common_env_data.platform_espressif32} diff --git a/publish_firmware.py b/publish_firmware.py new file mode 100644 index 00000000..7086d013 --- /dev/null +++ b/publish_firmware.py @@ -0,0 +1,71 @@ +# Copyright (c) 2014-present PlatformIO +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import requests +from os.path import basename +from platformio import util + +Import('env') + +project_config = util.load_project_config() +bintray_config = {k: v for k, v in project_config.items("bintray")} +version = project_config.get("common", "release_version") + +# +# Push new firmware to the Bintray storage using API +# + + +def publish_firmware(source, target, env): + firmware_path = str(source[0]) + firmware_name = basename(firmware_path) + + print("Uploading {0} to Bintray. Version: {1}".format( + firmware_name, version)) + + print(firmware_path, firmware_name) + + url = "/".join([ + "https://api.bintray.com", "content", + bintray_config.get("user"), + bintray_config.get("repository"), + bintray_config.get("package"), version, firmware_name + ]) + + print(url) + + headers = { + "Content-type": "application/octet-stream", + "X-Bintray-Publish": "1", + "X-Bintray-Override": "1" + } + + r = requests.put( + url, + data=open(firmware_path, "rb"), + headers=headers, + auth=(bintray_config.get("user"), bintray_config['api_token'])) + + if r.status_code != 201: + print("Failed to submit package: {0}\n{1}".format( + r.status_code, r.text)) + else: + print("The firmware has been successfuly published at Bintray.com!") + + +# Custom upload command and program name +env.Replace( + PROGNAME="firmware_v_%s" % version, + UPLOADCMD=publish_firmware +) \ No newline at end of file diff --git a/src/OTA.cpp b/src/OTA.cpp index 1ec3be8e..888c28cd 100644 --- a/src/OTA.cpp +++ b/src/OTA.cpp @@ -1,79 +1,226 @@ -#include "OTA.h" +/* + Parts of this code: + Copyright (c) 2014-present PlatformIO + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include +#include +#include +#include "ota.h" const BintrayClient bintray(BINTRAY_USER, BINTRAY_REPO, BINTRAY_PACKAGE); -bool Wifi_Connected = false; +// Connection port (HTTPS) +const int port = 443; -esp_err_t event_handler(void *ctx, system_event_t *event) { - switch (event->event_id) { - case SYSTEM_EVENT_STA_START: - esp_wifi_connect(); - ESP_LOGI(TAG, "Event STA_START"); - break; - case SYSTEM_EVENT_STA_GOT_IP: - Wifi_Connected = true; - ESP_LOGI(TAG, "Event STA_GOT_IP"); - // print the local IP address - tcpip_adapter_ip_info_t ip_info; - ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info)); - ESP_LOGI(TAG, "IP %s", ip4addr_ntoa(&ip_info.ip)); - break; - case SYSTEM_EVENT_STA_DISCONNECTED: - Wifi_Connected = false; - ESP_LOGI(TAG, "Event STA_DISCONNECTED"); - break; - default: - break; - } -} +// Connection timeout +const uint32_t RESPONSE_TIMEOUT_MS = 5000; -void ota_wifi_init(void) { +// Variables to validate firmware content +volatile int contentLength = 0; +volatile bool isValidContentType = false; - tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_STA; +// Local logging tag +static const char TAG[] = "main"; - // initialize the tcp stack - // nvs_flash_init(); - tcpip_adapter_init(); - tcpip_adapter_set_hostname(tcpip_if, PROGNAME); - tcpip_adapter_dhcpc_start(tcpip_if); +void start_ota_update() { + ota_update = false; // clear ota trigger switch - // initialize the wifi event handler - ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + ESP_LOGI(TAG, "Stopping Wifi scanner"); + vTaskDelete(WifiLoopTask); + ESP_LOGI(TAG, "Starting Wifi OTA update"); // switch off monitor more ESP_ERROR_CHECK( esp_wifi_set_promiscuous(false)); // now switch on monitor mode ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(NULL)); - wifi_sta_config_t cfg; - strcpy((char *)cfg.ssid, WIFI_SSID); - strcpy((char *)cfg.password, WIFI_PASS); - cfg.bssid_set = false; + WiFi.begin(WIFI_SSID, WIFI_PASS); - wifi_config_t sta_cfg; - sta_cfg.sta = cfg; + while (WiFi.status() != WL_CONNECTED) { + delay(2000); + ESP_LOGI(TAG, "trying to connect to %s", WIFI_SSID); + } - wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_LOGI(TAG, "connected to %s", WIFI_SSID); - ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg)); - ESP_ERROR_CHECK( - esp_wifi_set_storage(WIFI_STORAGE_RAM)); // we don't need NVRAM - ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); - ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &sta_cfg)); - ESP_ERROR_CHECK(esp_wifi_start()); + checkFirmwareUpdates(); + ESP.restart(); // reached only if update was not successful + +} // start_ota_update + +void checkFirmwareUpdates() { + // Fetch the latest firmware version + ESP_LOGI(TAG, "Checking latest firmware version..."); + const String latest = bintray.getLatestVersion(); + if (latest.length() == 0) { + ESP_LOGI(TAG, "Could not load info about the latest firmware, so nothing " + "to update. Continue ..."); + return; + } else if (atoi(latest.c_str()) <= VERSION) { + ESP_LOGI(TAG, "The current firmware is up to date. Continue ..."); + return; + } + + ESP_LOGI(TAG, "There is a new version of firmware available: v.%s", + latest.c_str()); + processOTAUpdate(latest); } -void start_ota_update() { - ESP_LOGI(TAG, "Stopping Wifi task on core 0"); - vTaskDelete(WifiLoopTask); +// A helper function to extract header value from header +inline String getHeaderValue(String header, String headerName) { + return header.substring(strlen(headerName.c_str())); +} - ESP_LOGI(TAG, "Stopping LORA task on core 1"); - vTaskDelete(LoraTask); +/** + * OTA update processing + */ +void processOTAUpdate(const String &version) { + String firmwarePath = bintray.getBinaryPath(version); + if (!firmwarePath.endsWith(".bin")) { + ESP_LOGI(TAG, "Unsupported binary format. OTA update cannot be performed!"); + return; + } - ESP_LOGI(TAG, "Connecting to %s", WIFI_SSID); - ota_wifi_init(); - delay(2000); - delay(2000); - checkFirmwareUpdates(); - ESP.restart(); // reached if update was not successful + String currentHost = bintray.getStorageHost(); + String prevHost = currentHost; + + WiFiClientSecure client; + client.setCACert(bintray.getCertificate(currentHost)); + + if (!client.connect(currentHost.c_str(), port)) { + ESP_LOGI(TAG, "Cannot connect to %s", currentHost.c_str()); + return; + } + + bool redirect = true; + while (redirect) { + if (currentHost != prevHost) { + client.stop(); + client.setCACert(bintray.getCertificate(currentHost)); + if (!client.connect(currentHost.c_str(), port)) { + ESP_LOGI(TAG, + "Redirect detected! Cannot connect to %s for some reason!", + currentHost.c_str()); + return; + } + } + + // ESP_LOGI(TAG, "Requesting: " + firmwarePath); + + client.print(String("GET ") + firmwarePath + " HTTP/1.1\r\n"); + client.print(String("Host: ") + currentHost + "\r\n"); + client.print("Cache-Control: no-cache\r\n"); + client.print("Connection: close\r\n\r\n"); + + unsigned long timeout = millis(); + while (client.available() == 0) { + if (millis() - timeout > RESPONSE_TIMEOUT_MS) { + ESP_LOGI(TAG, "Client Timeout !"); + client.stop(); + return; + } + } + + while (client.available()) { + String line = client.readStringUntil('\n'); + // Check if the line is end of headers by removing space symbol + line.trim(); + // if the the line is empty, this is the end of the headers + if (!line.length()) { + break; // proceed to OTA update + } + + // Check allowed HTTP responses + if (line.startsWith("HTTP/1.1")) { + if (line.indexOf("200") > 0) { + ESP_LOGI(TAG, "Got 200 status code from server. Proceeding to " + "firmware flashing"); + redirect = false; + } else if (line.indexOf("302") > 0) { + ESP_LOGI(TAG, "Got 302 status code from server. Redirecting to the " + "new address"); + redirect = true; + } else { + ESP_LOGI(TAG, "Could not get a valid firmware url"); + // Unexptected HTTP response. Retry or skip update? + redirect = false; + } + } + + // Extracting new redirect location + if (line.startsWith("Location: ")) { + String newUrl = getHeaderValue(line, "Location: "); + ESP_LOGI(TAG, "Got new url: %s", newUrl.c_str()); + newUrl.remove(0, newUrl.indexOf("//") + 2); + currentHost = newUrl.substring(0, newUrl.indexOf('/')); + newUrl.remove(newUrl.indexOf(currentHost), currentHost.length()); + firmwarePath = newUrl; + ESP_LOGI(TAG, "firmwarePath: %s", firmwarePath.c_str()); + continue; + } + + // Checking headers + if (line.startsWith("Content-Length: ")) { + contentLength = + atoi((getHeaderValue(line, "Content-Length: ")).c_str()); + ESP_LOGI(TAG, "Got %d bytes from server", contentLength); + } + + if (line.startsWith("Content-Type: ")) { + String contentType = getHeaderValue(line, "Content-Type: "); + ESP_LOGI(TAG, "Got %s payload", contentType.c_str()); + if (contentType == "application/octet-stream") { + isValidContentType = true; + } + } + } + } + + // check whether we have everything for OTA update + if (contentLength && isValidContentType) { + if (Update.begin(contentLength)) { + ESP_LOGI(TAG, "Starting Over-The-Air update. This may take some time to " + "complete ..."); + size_t written = Update.writeStream(client); + + if (written == contentLength) { + ESP_LOGI(TAG, "Written %d successfully", written); + } else { + ESP_LOGI(TAG, "Written only %d / %d Retry?", written, contentLength); + // Retry?? + } + + if (Update.end()) { + if (Update.isFinished()) { + ESP_LOGI(TAG, "OTA update has successfully completed. Rebooting ..."); + ESP.restart(); + } else { + ESP_LOGI(TAG, "Something went wrong! OTA update hasn't been finished " + "properly."); + } + } else { + ESP_LOGI(TAG, "An error occurred. Error #: %d", Update.getError()); + } + } else { + ESP_LOGI(TAG, "There isn't enough space to start OTA update"); + client.flush(); + } + } else { + ESP_LOGI(TAG, + "There was no valid content in the response from the OTA server!"); + client.flush(); + } } \ No newline at end of file diff --git a/src/OTA.h b/src/OTA.h index 8b6a27df..a20e54df 100644 --- a/src/OTA.h +++ b/src/OTA.h @@ -2,12 +2,12 @@ #define OTA_H #include -#include "globals.h" -#include #include -#include "ota.h" -#include "SecureOTA.h" +#include "globals.h" +#include "wifiscan.h" +void checkFirmwareUpdates(); +void processOTAUpdate(const String &version); void start_ota_update(); #endif // OTA_H \ No newline at end of file diff --git a/src/SecureOTA.cpp b/src/SecureOTA.cpp deleted file mode 100644 index 2ce69eca..00000000 --- a/src/SecureOTA.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/* - Copyright (c) 2014-present PlatformIO - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -**/ - -#include -#include -#include -#include "SecureOTA.h" - -const BintrayClient bintray(BINTRAY_USER, BINTRAY_REPO, BINTRAY_PACKAGE); - -// Connection port (HTTPS) -const int port = 443; - -// Connection timeout -const uint32_t RESPONSE_TIMEOUT_MS = 5000; - -// Variables to validate firmware content -volatile int contentLength = 0; -volatile bool isValidContentType = false; - -void checkFirmwareUpdates() -{ - // Fetch the latest firmware version - const String latest = bintray.getLatestVersion(); - if (latest.length() == 0) - { - ESP_LOGI(TAG, "Could not load info about the latest firmware, so nothing to update. Continue ..."); - return; - } - else if (atoi(latest.c_str()) <= VERSION) - { - //ESP_LOGI(TAG, "The current firmware is up to date. Continue ..."); - return; - } - - ESP_LOGI(TAG, "There is a new version of firmware available: v.%s", latest); - processOTAUpdate(latest); -} - -// A helper function to extract header value from header -inline String getHeaderValue(String header, String headerName) -{ - return header.substring(strlen(headerName.c_str())); -} - -/** - * OTA update processing - */ -void processOTAUpdate(const String &version) -{ - String firmwarePath = bintray.getBinaryPath(version); - if (!firmwarePath.endsWith(".bin")) - { - ESP_LOGI(TAG, "Unsupported binary format. OTA update cannot be performed!"); - return; - } - - String currentHost = bintray.getStorageHost(); - String prevHost = currentHost; - - WiFiClientSecure client; - client.setCACert(bintray.getCertificate(currentHost)); - - if (!client.connect(currentHost.c_str(), port)) - { - ESP_LOGI(TAG, "Cannot connect to %s", currentHost); - return; - } - - bool redirect = true; - while (redirect) - { - if (currentHost != prevHost) - { - client.stop(); - client.setCACert(bintray.getCertificate(currentHost)); - if (!client.connect(currentHost.c_str(), port)) - { - ESP_LOGI(TAG, "Redirect detected! Cannot connect to %s for some reason!", currentHost); - return; - } - } - - //ESP_LOGI(TAG, "Requesting: " + firmwarePath); - - client.print(String("GET ") + firmwarePath + " HTTP/1.1\r\n"); - client.print(String("Host: ") + currentHost + "\r\n"); - client.print("Cache-Control: no-cache\r\n"); - client.print("Connection: close\r\n\r\n"); - - unsigned long timeout = millis(); - while (client.available() == 0) - { - if (millis() - timeout > RESPONSE_TIMEOUT_MS) - { - ESP_LOGI(TAG, "Client Timeout !"); - client.stop(); - return; - } - } - - while (client.available()) - { - String line = client.readStringUntil('\n'); - // Check if the line is end of headers by removing space symbol - line.trim(); - // if the the line is empty, this is the end of the headers - if (!line.length()) - { - break; // proceed to OTA update - } - - // Check allowed HTTP responses - if (line.startsWith("HTTP/1.1")) - { - if (line.indexOf("200") > 0) - { - ESP_LOGI(TAG, "Got 200 status code from server. Proceeding to firmware flashing"); - redirect = false; - } - else if (line.indexOf("302") > 0) - { - ESP_LOGI(TAG, "Got 302 status code from server. Redirecting to the new address"); - redirect = true; - } - else - { - ESP_LOGI(TAG, "Could not get a valid firmware url"); - //Unexptected HTTP response. Retry or skip update? - redirect = false; - } - } - - // Extracting new redirect location - if (line.startsWith("Location: ")) - { - String newUrl = getHeaderValue(line, "Location: "); - ESP_LOGI(TAG, "Got new url: %s", newUrl); - newUrl.remove(0, newUrl.indexOf("//") + 2); - currentHost = newUrl.substring(0, newUrl.indexOf('/')); - newUrl.remove(newUrl.indexOf(currentHost), currentHost.length()); - firmwarePath = newUrl; - ESP_LOGI(TAG, "firmwarePath: %s", firmwarePath); - continue; - } - - // Checking headers - if (line.startsWith("Content-Length: ")) - { - contentLength = atoi((getHeaderValue(line, "Content-Length: ")).c_str()); - ESP_LOGI(TAG, "Got %s bytes from server", String(contentLength)); - } - - if (line.startsWith("Content-Type: ")) - { - String contentType = getHeaderValue(line, "Content-Type: "); - ESP_LOGI(TAG, "Got %s payload", contentType); - if (contentType == "application/octet-stream") - { - isValidContentType = true; - } - } - } - } - - // check whether we have everything for OTA update - if (contentLength && isValidContentType) - { - if (Update.begin(contentLength)) - { - ESP_LOGI(TAG, "Starting Over-The-Air update. This may take some time to complete ..."); - size_t written = Update.writeStream(client); - - if (written == contentLength) - { - ESP_LOGI(TAG, "Written %s successfully", String(written)); - } - else - { - ESP_LOGI(TAG, "Written only %s / %s Retry?", String(written), String(contentLength)); - // Retry?? - } - - if (Update.end()) - { - if (Update.isFinished()) - { - ESP_LOGI(TAG, "OTA update has successfully completed. Rebooting ..."); - ESP.restart(); - } - else - { - ESP_LOGI(TAG, "Something went wrong! OTA update hasn't been finished properly."); - } - } - else - { - ESP_LOGI(TAG, "An error occurred. Error #: %s", String(Update.getError())); - } - } - else - { - ESP_LOGI(TAG, "There isn't enough space to start OTA update"); - client.flush(); - } - } - else - { - ESP_LOGI(TAG, "There was no valid content in the response from the OTA server!"); - client.flush(); - } -} \ No newline at end of file diff --git a/src/SecureOTA.h b/src/SecureOTA.h deleted file mode 100644 index d8b17c3f..00000000 --- a/src/SecureOTA.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - Copyright (c) 2014-present PlatformIO - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -**/ - -#ifndef SECURE_OTA_H -#define SECURE_OTA_H - -#include - -void checkFirmwareUpdates(); -void processOTAUpdate(const String &version); - -#endif // SECURE_OTA_H \ No newline at end of file