diff --git a/lib/BintrayClient/library.json b/lib/BintrayClient/library.json new file mode 100644 index 00000000..677cec5c --- /dev/null +++ b/lib/BintrayClient/library.json @@ -0,0 +1,24 @@ +{ + "name": "BintrayClient", + "keywords": "bintray, ota, cdn, storage", + "description": "A BintrayClient to connect to a JFrog Bintray.", + "authors": [ + { + "name": "PlatformIO", + "url": "https://platformio.org/" + } + ], + "repository": { + "type": "git", + "url": "https://github.com/platformio/platformio-examples" + }, + "export": { + "include": "bintray-secure-ota/lib/BintrayClient" + }, + "dependencies": { + "ArduinoJson": "^5.13.1" + }, + "version": "1.0.0", + "frameworks": "arduino", + "platforms": "espressif32" +} diff --git a/lib/BintrayClient/src/BintrayCertificates.h b/lib/BintrayClient/src/BintrayCertificates.h new file mode 100644 index 00000000..ec5fdaa3 --- /dev/null +++ b/lib/BintrayClient/src/BintrayCertificates.h @@ -0,0 +1,102 @@ +/* + 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 BINTRAY_CERTIFICATES_H +#define BINTRAY_CERTIFICATES_H + +const char* BINTRAY_API_ROOT_CA = \ +"-----BEGIN CERTIFICATE-----\n" \ +"MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT\n" \ +"MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i\n" \ +"YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG\n" \ +"EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg\n" \ +"R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9\n" \ +"9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq\n" \ +"fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv\n" \ +"iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU\n" \ +"1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+\n" \ +"bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW\n" \ +"MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA\n" \ +"ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l\n" \ +"uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn\n" \ +"Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS\n" \ +"tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF\n" \ +"PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un\n" \ +"hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV\n" \ +"5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==\n" \ +"-----END CERTIFICATE-----\n"; + +const char* BINTRAY_AKAMAI_ROOT_CA = \ +"-----BEGIN CERTIFICATE-----\n"\ +"MIIElDCCA3ygAwIBAgIQAf2j627KdciIQ4tyS8+8kTANBgkqhkiG9w0BAQsFADBh\n"\ +"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"\ +"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"\ +"QTAeFw0xMzAzMDgxMjAwMDBaFw0yMzAzMDgxMjAwMDBaME0xCzAJBgNVBAYTAlVT\n"\ +"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxJzAlBgNVBAMTHkRpZ2lDZXJ0IFNIQTIg\n"\ +"U2VjdXJlIFNlcnZlciBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\n"\ +"ANyuWJBNwcQwFZA1W248ghX1LFy949v/cUP6ZCWA1O4Yok3wZtAKc24RmDYXZK83\n"\ +"nf36QYSvx6+M/hpzTc8zl5CilodTgyu5pnVILR1WN3vaMTIa16yrBvSqXUu3R0bd\n"\ +"KpPDkC55gIDvEwRqFDu1m5K+wgdlTvza/P96rtxcflUxDOg5B6TXvi/TC2rSsd9f\n"\ +"/ld0Uzs1gN2ujkSYs58O09rg1/RrKatEp0tYhG2SS4HD2nOLEpdIkARFdRrdNzGX\n"\ +"kujNVA075ME/OV4uuPNcfhCOhkEAjUVmR7ChZc6gqikJTvOX6+guqw9ypzAO+sf0\n"\ +"/RR3w6RbKFfCs/mC/bdFWJsCAwEAAaOCAVowggFWMBIGA1UdEwEB/wQIMAYBAf8C\n"\ +"AQAwDgYDVR0PAQH/BAQDAgGGMDQGCCsGAQUFBwEBBCgwJjAkBggrBgEFBQcwAYYY\n"\ +"aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMHsGA1UdHwR0MHIwN6A1oDOGMWh0dHA6\n"\ +"Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RDQS5jcmwwN6A1\n"\ +"oDOGMWh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEdsb2JhbFJvb3RD\n"\ +"QS5jcmwwPQYDVR0gBDYwNDAyBgRVHSAAMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8v\n"\ +"d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwHQYDVR0OBBYEFA+AYRyCMWHVLyjnjUY4tCzh\n"\ +"xtniMB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA0GCSqGSIb3DQEB\n"\ +"CwUAA4IBAQAjPt9L0jFCpbZ+QlwaRMxp0Wi0XUvgBCFsS+JtzLHgl4+mUwnNqipl\n"\ +"5TlPHoOlblyYoiQm5vuh7ZPHLgLGTUq/sELfeNqzqPlt/yGFUzZgTHbO7Djc1lGA\n"\ +"8MXW5dRNJ2Srm8c+cftIl7gzbckTB+6WohsYFfZcTEDts8Ls/3HB40f/1LkAtDdC\n"\ +"2iDJ6m6K7hQGrn2iWZiIqBtvLfTyyRRfJs8sjX7tN8Cp1Tm5gr8ZDOo0rwAhaPit\n"\ +"c+LJMto4JQtV05od8GiG7S5BNO98pVAdvzr508EIDObtHopYJeS4d60tbvVS3bR0\n"\ +"j6tJLp07kzQoH3jOlOrHvdPJbRzeXDLz\n"\ +"-----END CERTIFICATE-----\n"; + +const char* CLOUDFRONT_API_ROOT_CA = \ +"-----BEGIN CERTIFICATE-----\n"\ +"MIIE3zCCA8egAwIBAgIQYxgNOPuAl3ip0DWjFhj4QDANBgkqhkiG9w0BAQsFADCB\n"\ +"yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL\n"\ +"ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp\n"\ +"U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW\n"\ +"ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0\n"\ +"aG9yaXR5IC0gRzUwHhcNMTcxMTA2MDAwMDAwWhcNMjIxMTA1MjM1OTU5WjBhMQsw\n"\ +"CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu\n"\ +"ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMjCC\n"\ +"ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALs3zTTce2vJsmiQrUp1/0a6\n"\ +"IQoIjfUZVMn7iNvzrvI6iZE8euarBhprz6wt6F4JJES6Ypp+1qOofuBUdSAFrFC3\n"\ +"nGMabDDc2h8Zsdce3v3X4MuUgzeu7B9DTt17LNK9LqUv5Km4rTrUmaS2JembawBg\n"\ +"kmD/TyFJGPdnkKthBpyP8rrptOmSMmu181foXRvNjB2rlQSVSfM1LZbjSW3dd+P7\n"\ +"SUu0rFUHqY+Vs7Qju0xtRfD2qbKVMLT9TFWMJ0pXFHyCnc1zktMWSgYMjFDRjx4J\n"\ +"vheh5iHK/YPlELyDpQrEZyj2cxQUPUZ2w4cUiSE0Ta8PRQymSaG6u5zFsTODKYUC\n"\ +"AwEAAaOCAScwggEjMB0GA1UdDgQWBBROIlQgGJXm427mD/r6uRLtBhePOTAPBgNV\n"\ +"HRMBAf8EBTADAQH/MF8GA1UdIARYMFYwVAYEVR0gADBMMCMGCCsGAQUFBwIBFhdo\n"\ +"dHRwczovL2Quc3ltY2IuY29tL2NwczAlBggrBgEFBQcCAjAZDBdodHRwczovL2Qu\n"\ +"c3ltY2IuY29tL3JwYTAvBgNVHR8EKDAmMCSgIqAghh5odHRwOi8vcy5zeW1jYi5j\n"\ +"b20vcGNhMy1nNS5jcmwwDgYDVR0PAQH/BAQDAgGGMC4GCCsGAQUFBwEBBCIwIDAe\n"\ +"BggrBgEFBQcwAYYSaHR0cDovL3Muc3ltY2QuY29tMB8GA1UdIwQYMBaAFH/TZafC\n"\ +"3ey78DAJ80M5+gKvMzEzMA0GCSqGSIb3DQEBCwUAA4IBAQBQ3dNWKSUBip6n5X1N\n"\ +"ua8bjKLSJzXlnescavPECMpFBlIIKH2mc6mL2Xr/wkSIBDrsqAO3sBcmoJN+n8V3\n"\ +"0O5JelrtEAFYSyRDXfu78ZlHn6kvV5/jPUFECEM/hdN0x8WdLpGjJMqfs0EG5qHj\n"\ +"+UaxpucWD445wea4zlK7hUR+MA8fq0Yd1HEKj4c8TcgaQIHMa4KHr448cQ69e3CP\n"\ +"ECRhRNg+RAKT2I7SlaVzLvaB/8yym2oMCEsoqiRT8dbXg35aKEYmmzn3O/mnB7bG\n"\ +"Ud/EUrkIf7FVamgYZd1fSzQeg1cHqf0ja6eHpvq2bTl+cWFHaq/84KlHe5Rh0Csm\n"\ +"pZzn\n"\ +"-----END CERTIFICATE-----\n"; + +#endif // BINTRAY_CERTIFICATES_H diff --git a/lib/BintrayClient/src/BintrayClient.cpp b/lib/BintrayClient/src/BintrayClient.cpp new file mode 100644 index 00000000..4479f539 --- /dev/null +++ b/lib/BintrayClient/src/BintrayClient.cpp @@ -0,0 +1,149 @@ +/* + 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 "BintrayClient.h" +#include "BintrayCertificates.h" + +BintrayClient::BintrayClient(const String &user, const String &repository, const String &package) + : m_user(user), m_repo(repository), m_package(package), + m_storage_host("dl.bintray.com"), + m_api_host("api.bintray.com") +{ + m_certificates.emplace_back("cloudfront.net", CLOUDFRONT_API_ROOT_CA); + m_certificates.emplace_back("akamai.bintray.com", BINTRAY_AKAMAI_ROOT_CA); + m_certificates.emplace_back("bintray.com", BINTRAY_API_ROOT_CA); +} + +String BintrayClient::getUser() const +{ + return m_user; +} + +String BintrayClient::getRepository() const +{ + return m_repo; +} + +String BintrayClient::getPackage() const +{ + return m_package; +} + +String BintrayClient::getStorageHost() const +{ + return m_storage_host; +} + +String BintrayClient::getApiHost() const +{ + return m_api_host; +} + +String BintrayClient::getLatestVersionRequestUrl() const +{ + return String("https://") + getApiHost() + "/packages/" + getUser() + "/" + getRepository() + "/" + getPackage() + "/versions/_latest"; +} + +String BintrayClient::getBinaryRequestUrl(const String &version) const +{ + return String("https://") + getApiHost() + "/packages/" + getUser() + "/" + getRepository() + "/" + getPackage() + "/versions/" + version + "/files"; +} + +const char *BintrayClient::getCertificate(const String &url) const +{ + for(auto& cert: m_certificates) { + if(url.indexOf(cert.first) >= 0) { + return cert.second; + } + } + + // Return the certificate for *.bintray.com by default + return m_certificates.rbegin()->second; +} + +String BintrayClient::requestHTTPContent(const String &url) const +{ + String payload; + HTTPClient http; + http.begin(url, getCertificate(url)); + int httpCode = http.GET(); + + if (httpCode > 0) + { + if (httpCode == HTTP_CODE_OK) + { + payload = http.getString(); + } + } + else + { + Serial.printf("GET request failed, error: %s\n", http.errorToString(httpCode).c_str()); + } + + http.end(); + return payload; +} + +String BintrayClient::getLatestVersion() const +{ + String version; + const String url = getLatestVersionRequestUrl(); + String jsonResult = requestHTTPContent(url); + const size_t bufferSize = 1024; + if (jsonResult.length() > bufferSize) + { + ESP_LOGI(TAG, "Error: Could not parse JSON. Input data is too big!"); + return version; + } + StaticJsonBuffer jsonBuffer; + + JsonObject &root = jsonBuffer.parseObject(jsonResult.c_str()); + // Check for errors in parsing + if (!root.success()) + { + ESP_LOGI(TAG, "Error: Could not parse JSON!"); + return version; + } + return root.get("name"); +} + +String BintrayClient::getBinaryPath(const String &version) const +{ + String path; + const String url = getBinaryRequestUrl(version); + String jsonResult = requestHTTPContent(url); + + const size_t bufferSize = 1024; + if (jsonResult.length() > bufferSize) + { + ESP_LOGI(TAG, "Error: Could parse JSON. Input data is too big!"); + return path; + } + StaticJsonBuffer jsonBuffer; + + JsonArray &root = jsonBuffer.parseArray(jsonResult.c_str()); + JsonObject &firstItem = root[0]; + if (!root.success()) + { //Check for errors in parsing + ESP_LOGI(TAG, "Error: Could not parse JSON!"); + return path; + } + return "/" + getUser() + "/" + getRepository() + "/" + firstItem.get("path"); +} diff --git a/lib/BintrayClient/src/BintrayClient.h b/lib/BintrayClient/src/BintrayClient.h new file mode 100644 index 00000000..9761c7c6 --- /dev/null +++ b/lib/BintrayClient/src/BintrayClient.h @@ -0,0 +1,49 @@ +/* + 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 BINTRAY_CLIENT_H +#define BINTRAY_CLIENT_H + +#include +#include +#include + +class BintrayClient { + +public: + BintrayClient(const String& user, const String& repository, const String& package); + String getUser() const; + String getRepository() const; + String getPackage() const; + String getStorageHost() const; + String getApiHost() const; + const char* getCertificate(const String& url) const; + String getLatestVersion() const; + String getBinaryPath(const String& version) const; + +private: + String requestHTTPContent(const String& url) const; + String getLatestVersionRequestUrl() const; + String getBinaryRequestUrl(const String& version) const; + String m_user; + String m_repo; + String m_package; + const String m_storage_host; + const String m_api_host; + std::vector> m_certificates; +}; + +#endif // BINTRAY_CLIENT_H diff --git a/platformio.ini b/platformio.ini index bb895931..45f6672b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -11,12 +11,12 @@ ; ---> SELECT TARGET PLATFORM HERE! <--- [platformio] -env_default = generic +;env_default = generic ;env_default = ebox ;env_default = heltec ;env_default = ttgov1 ;env_default = ttgov2 -;env_default = ttgov21 +env_default = ttgov21 ;env_default = ttgobeam ;env_default = lopy ;env_default = lopy4 @@ -27,11 +27,55 @@ env_default = generic ; description = Paxcounter is a proof-of-concept ESP32 device for metering passenger flows in realtime. It counts how many mobile devices are around. +[bintray] +user = cyberman54 +repository = paxcounter +package = esp32-paxcounter +api_token = *** + +[wifi] +ssid = *** +password = *** + +[common] +platform = https://github.com/platformio/platform-espressif32.git + +; firmware version, please modify it between releases +; positive integer value +release_version = 4 + +; build configuration based on Bintray and Wi-Fi settings +build_flags = + '-DWIFI_SSID="${wifi.ssid}"' + '-DWIFI_PASS="${wifi.password}"' + '-DBINTRAY_USER="${bintray.user}"' + '-DBINTRAY_REPO="${bintray.repository}"' + '-DBINTRAY_PACKAGE="${bintray.package}"' + -DVERSION=${common.release_version} +; +; ---> NOTE: For production run set DEBUG_LEVEL level to NONE! <--- +; otherwise device may leak RAM +; +; None +; -DCORE_DEBUG_LEVEL=0 +; Error +; -DCORE_DEBUG_LEVEL=1 +; Warn +; -DCORE_DEBUG_LEVEL=2 +; Info + -DCORE_DEBUG_LEVEL=3 +; Debug +; -DCORE_DEBUG_LEVEL=4 +; Verbose +; -DCORE_DEBUG_LEVEL=5 + [common_env_data] platform_espressif32 = espressif32@1.3.0 ;platform_espressif32 = https://github.com/platformio/platform-espressif32.git#feature/stage -board_build.partitions = no_ota.csv -lib_deps_all = +;board_build.partitions = no_ota.csv +board_build.partitions = min_spiffs.csv +lib_deps_all = + ArduinoJson lib_deps_display = U8g2@>=2.23.16 lib_deps_rgbled = @@ -40,28 +84,12 @@ lib_deps_gps = TinyGPSPlus@>=1.0.2 Time@>=1.5 build_flags = -; ---> NOTE: For production run set DEBUG_LEVEL level to NONE! <--- -; otherwise device may leak RAM -; -; None - -DCORE_DEBUG_LEVEL=0 -; Error -; -DCORE_DEBUG_LEVEL=1 -; Warn -; -DCORE_DEBUG_LEVEL=2 -; Info -; -DCORE_DEBUG_LEVEL=3 -; Debug -; -DCORE_DEBUG_LEVEL=4 -; Verbose -; -DCORE_DEBUG_LEVEL=5 -; ; override lora settings from LMiC library in lmic/config.h and use main.h instead -D_lmic_config_h_ -include "src/paxcounter.conf" -include "src/hal/${PIOENV}.h" -w - + [env:ebox] platform = ${common_env_data.platform_espressif32} framework = arduino @@ -124,6 +152,7 @@ lib_deps = ${common_env_data.lib_deps_all} ${common_env_data.lib_deps_display} build_flags = + ${common.build_flags} ${common_env_data.build_flags} [env:ttgobeam] diff --git a/src/OTA.cpp b/src/OTA.cpp new file mode 100644 index 00000000..1ec3be8e --- /dev/null +++ b/src/OTA.cpp @@ -0,0 +1,79 @@ +#include "OTA.h" + +const BintrayClient bintray(BINTRAY_USER, BINTRAY_REPO, BINTRAY_PACKAGE); + +bool Wifi_Connected = false; + +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; + } +} + +void ota_wifi_init(void) { + + tcpip_adapter_if_t tcpip_if = TCPIP_ADAPTER_IF_STA; + + // initialize the tcp stack + // nvs_flash_init(); + tcpip_adapter_init(); + tcpip_adapter_set_hostname(tcpip_if, PROGNAME); + tcpip_adapter_dhcpc_start(tcpip_if); + + // initialize the wifi event handler + ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); + + // 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_config_t sta_cfg; + sta_cfg.sta = cfg; + + wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT(); + + 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()); +} + +void start_ota_update() { + ESP_LOGI(TAG, "Stopping Wifi task on core 0"); + vTaskDelete(WifiLoopTask); + + ESP_LOGI(TAG, "Stopping LORA task on core 1"); + vTaskDelete(LoraTask); + + ESP_LOGI(TAG, "Connecting to %s", WIFI_SSID); + ota_wifi_init(); + delay(2000); + delay(2000); + checkFirmwareUpdates(); + ESP.restart(); // reached if update was not successful +} \ No newline at end of file diff --git a/src/OTA.h b/src/OTA.h new file mode 100644 index 00000000..8b6a27df --- /dev/null +++ b/src/OTA.h @@ -0,0 +1,13 @@ +#ifndef OTA_H +#define OTA_H + +#include +#include "globals.h" +#include +#include +#include "ota.h" +#include "SecureOTA.h" + +void start_ota_update(); + +#endif // OTA_H \ No newline at end of file diff --git a/src/SecureOTA.cpp b/src/SecureOTA.cpp new file mode 100644 index 00000000..2ce69eca --- /dev/null +++ b/src/SecureOTA.cpp @@ -0,0 +1,225 @@ +/* + 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 new file mode 100644 index 00000000..d8b17c3f --- /dev/null +++ b/src/SecureOTA.h @@ -0,0 +1,25 @@ +/* + 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 diff --git a/src/TTN/packed_decoder.js b/src/TTN/packed_decoder.js index 21097634..9aaf1cfc 100644 --- a/src/TTN/packed_decoder.js +++ b/src/TTN/packed_decoder.js @@ -21,7 +21,6 @@ function Decoder(bytes, port) { return decode(bytes, [uint16, uptime, uint8, uint32], ['voltage', 'uptime', 'cputemp', 'memory']); } - if (port === 3) { // device config data return decode(bytes, [uint8, uint8, uint16, uint8, uint8, uint8, uint8, bitmap], ['lorasf', 'txpower', 'rssilimit', 'sendcycle', 'wifichancycle', 'blescantime', 'rgblum', 'flags']); @@ -181,6 +180,7 @@ if (typeof module === 'object' && typeof module.exports !== 'undefined') { uint16: uint16, uint32: uint32, uptime: uptime, + reset: reset, temperature: temperature, humidity: humidity, latLng: latLng, diff --git a/src/cyclic.cpp b/src/cyclic.cpp index adbadda4..45ce16cd 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -4,6 +4,7 @@ // Basic config #include "globals.h" #include "senddata.h" +#include "ota.h" // Local logging tag static const char TAG[] = "main"; @@ -14,6 +15,9 @@ void doHomework() { // update uptime counter uptime(); + if (ota_update) + start_ota_update(); + // read battery voltage into global variable #ifdef HAS_BATTERY_PROBE batt_voltage = read_voltage(); diff --git a/src/globals.h b/src/globals.h index a34851a7..53aad820 100644 --- a/src/globals.h +++ b/src/globals.h @@ -42,7 +42,8 @@ typedef struct { } MessageBuffer_t; // global variables -extern configData_t cfg; // current device configuration +extern configData_t cfg; // current device configuration +extern bool ota_update; extern char display_line6[], display_line7[]; // screen buffers extern uint8_t channel; // wifi channel rotation counter extern uint16_t macs_total, macs_wifi, macs_ble, batt_voltage; // display values @@ -51,7 +52,8 @@ extern hw_timer_t *channelSwitch, *sendCycle; extern portMUX_TYPE timerMux; extern volatile int SendCycleTimerIRQ, HomeCycleIRQ, DisplayTimerIRQ, ChannelTimerIRQ, ButtonPressedIRQ; -extern QueueHandle_t LoraSendQueue, SPISendQueue; +// extern QueueHandle_t LoraSendQueue, SPISendQueue; +extern TaskHandle_t WifiLoopTask; extern std::array::iterator it; extern std::array beacons; @@ -67,9 +69,15 @@ extern std::array beacons; #include "payload.h" #ifdef HAS_LORA +extern QueueHandle_t LoraSendQueue; +extern TaskHandle_t LoraTask; #include "lorawan.h" #endif +#ifdef HAS_SPI +extern QueueHandle_t SPISendQueue; +#endif + #ifdef HAS_DISPLAY #include "display.h" #endif diff --git a/src/main.cpp b/src/main.cpp index bd6d04f1..d52b4a7a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,8 @@ licenses. Refer to LICENSE.txt file in repository for more details. #include "globals.h" #include "main.h" -configData_t cfg; // struct holds current device configuration +configData_t cfg; // struct holds current device configuration +bool ota_update = false; // triggers OTA update char display_line6[16], display_line7[16]; // display buffers uint8_t channel = 0; // channel rotation counter uint16_t macs_total = 0, macs_wifi = 0, macs_ble = 0, @@ -41,9 +42,12 @@ hw_timer_t *channelSwitch = NULL, *displaytimer = NULL, *sendCycle = NULL, volatile int ButtonPressedIRQ = 0, ChannelTimerIRQ = 0, SendCycleTimerIRQ = 0, DisplayTimerIRQ = 0, HomeCycleIRQ = 0; +TaskHandle_t WifiLoopTask = NULL; + // RTos send queues for payload transmit #ifdef HAS_LORA QueueHandle_t LoraSendQueue; +TaskHandle_t LoraTask = NULL; #endif #ifdef HAS_SPI @@ -67,6 +71,9 @@ static const char TAG[] = "main"; void setup() { + // disable the default wifi logging + esp_log_level_set("wifi", ESP_LOG_NONE); + char features[100] = ""; // disable brownout detection @@ -89,7 +96,8 @@ void setup() { // initialize system event handler for wifi task, needed for // wifi_sniffer_init() - esp_event_loop_init(NULL, NULL); + // 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 @@ -269,7 +277,7 @@ void setup() { ESP_LOGI(TAG, "Starting Lora task on core 1"); xTaskCreatePinnedToCore(lorawan_loop, "loraloop", 2048, (void *)1, - (5 | portPRIVILEGE_BIT), NULL, 1); + (5 | portPRIVILEGE_BIT), &LoraTask, 1); #endif // if device has GPS and it is enabled, start GPS reader task on core 0 with @@ -298,7 +306,7 @@ void setup() { // gets it's seed from RF noise reset_salt(); // get new 16bit for salting hashes xTaskCreatePinnedToCore(wifi_channel_loop, "wifiloop", 2048, (void *)1, 1, - NULL, 0); + &WifiLoopTask, 0); } // setup() /* end Arduino SETUP @@ -330,7 +338,7 @@ void loop() { processSendBuffer(); // check send cycle and enqueue payload if cycle is expired sendPayload(); - // reset watchdog + // reset watchdog vTaskDelay(1 / portTICK_PERIOD_MS); } // loop() diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 7cf76677..bcecc56d 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -9,7 +9,7 @@ // Payload send cycle and encoding #define SEND_SECS 30 // payload send cycle [seconds/2] -> 60 sec. -#define PAYLOAD_ENCODER 1 // payload encoder: 1=Plain, 2=Packed, 3=CayenneLPP dynamic, 4=CayenneLPP packed +#define PAYLOAD_ENCODER 2 // payload encoder: 1=Plain, 2=Packed, 3=CayenneLPP dynamic, 4=CayenneLPP packed // Set this to include BLE counting and vendor filter functions #define VENDORFILTER 1 // comment out if you want to count things, not people diff --git a/src/payload.cpp b/src/payload.cpp index 87334b83..61514f94 100644 --- a/src/payload.cpp +++ b/src/payload.cpp @@ -54,6 +54,7 @@ void PayloadConvert::addConfig(configData_t value) { void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem) { + buffer[cursor++] = highByte(voltage); buffer[cursor++] = lowByte(voltage); buffer[cursor++] = (byte)((uptime & 0xFF00000000000000) >> 56); @@ -69,6 +70,8 @@ void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, buffer[cursor++] = (byte)((mem & 0x00FF0000) >> 16); buffer[cursor++] = (byte)((mem & 0x0000FF00) >> 8); buffer[cursor++] = (byte)((mem & 0x000000FF)); + buffer[cursor++] = (byte)(reset1); + buffer[cursor++] = (byte)(reset2); } #ifdef HAS_GPS @@ -123,12 +126,14 @@ void PayloadConvert::addConfig(configData_t value) { value.vendorfilter ? true : false, value.gpsmode ? true : false); } -void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, - float cputemp, uint32_t mem) { +void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float cputemp, + uint32_t mem, uint8_t reset1, uint8_t reset2) { writeUint16(voltage); writeUptime(uptime); writeUint8((byte)cputemp); writeUint32(mem); + writeUint8(reset1); + writeUint8(reset2); } #ifdef HAS_GPS @@ -241,8 +246,8 @@ void PayloadConvert::addConfig(configData_t value) { buffer[cursor++] = value.adrmode; } -void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, - float celsius, uint32_t mem) { +void PayloadConvert::addStatus(uint16_t voltage, uint64_t uptime, float celsius, + uint32_t mem, uint8_t reset1, uint8_t reset2) { uint16_t temp = celsius * 10; uint16_t volt = voltage / 10; #ifdef HAS_BATTERY_PROBE diff --git a/src/payload.h b/src/payload.h index ba564ec7..92f2d4ee 100644 --- a/src/payload.h +++ b/src/payload.h @@ -35,7 +35,8 @@ public: uint8_t *getBuffer(void); void addCount(uint16_t value1, uint16_t value2); void addConfig(configData_t value); - void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem); + void addStatus(uint16_t voltage, uint64_t uptime, float cputemp, uint32_t mem, + uint8_t reset1, uint8_t reset2); void addAlarm(int8_t rssi, uint8_t message); #ifdef HAS_GPS void addGPS(gpsStatus_t value); @@ -44,7 +45,6 @@ public: void addButton(uint8_t value); #endif - #if PAYLOAD_ENCODER == 1 // format plain private: @@ -77,7 +77,6 @@ private: #else #error "No valid payload converter defined" #endif - }; extern PayloadConvert payload; diff --git a/src/rcommand.cpp b/src/rcommand.cpp index 84af6cd6..2bae172a 100644 --- a/src/rcommand.cpp +++ b/src/rcommand.cpp @@ -203,7 +203,8 @@ void get_status(uint8_t val[]) { #endif payload.reset(); payload.addStatus(voltage, uptime() / 1000, temperatureRead(), - ESP.getFreeHeap()); + ESP.getFreeHeap(), rtc_get_reset_reason(0), + rtc_get_reset_reason(1)); SendData(STATUSPORT); }; @@ -219,6 +220,11 @@ void get_gps(uint8_t val[]) { #endif }; +void set_update(uint8_t val[]) { + ESP_LOGI(TAG, "Remote command: get firmware update"); + ota_update = true; +}; + // assign previously defined functions to set of numeric remote commands // format: opcode, function, #bytes params, // flag (1 = do make settings persistent / 0 = don't) @@ -233,8 +239,8 @@ cmd_t table[] = { {0x0d, set_vendorfilter, 1, false}, {0x0e, set_blescan, 1, true}, {0x0f, set_wifiant, 1, true}, {0x10, set_rgblum, 1, true}, {0x11, set_monitor, 1, true}, {0x12, set_beacon, 7, false}, - {0x80, get_config, 0, false}, {0x81, get_status, 0, false}, - {0x84, get_gps, 0, false}}; + {0x20, set_update, 0, false}, {0x80, get_config, 0, false}, + {0x81, get_status, 0, false}, {0x84, get_gps, 0, false}}; const uint8_t cmdtablesize = sizeof(table) / sizeof(table[0]); // number of commands in command table diff --git a/src/rcommand.h b/src/rcommand.h index 6afb905f..c656c9fd 100644 --- a/src/rcommand.h +++ b/src/rcommand.h @@ -5,6 +5,7 @@ #include "configmanager.h" #include "lorawan.h" #include "macsniff.h" +#include // table of remote commands and assigned functions typedef struct { diff --git a/src/wifiscan.cpp b/src/wifiscan.cpp index 4190e182..24d6504f 100644 --- a/src/wifiscan.cpp +++ b/src/wifiscan.cpp @@ -6,7 +6,7 @@ static const char TAG[] = "wifi"; static wifi_country_t wifi_country = {WIFI_MY_COUNTRY, WIFI_CHANNEL_MIN, - WIFI_CHANNEL_MAX, 0, + WIFI_CHANNEL_MAX, 100, WIFI_COUNTRY_POLICY_MANUAL}; // using IRAM_:ATTR here to speed up callback function @@ -36,6 +36,7 @@ void wifi_sniffer_init(void) { ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM)); // we don't need NVRAM ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL)); + ESP_ERROR_CHECK(esp_wifi_stop()); ESP_ERROR_CHECK( esp_wifi_set_promiscuous_filter(&filter)); // set MAC frame filter ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(&wifi_sniffer_packet_handler));