Merge pull request #152 from cyberman54/ota-test

Ota test
This commit is contained in:
Verkehrsrot 2018-09-15 16:32:16 +02:00 committed by GitHub
commit ef6f9c3dd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 290 additions and 317 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
.clang_complete .clang_complete
.gcc-flags.json .gcc-flags.json
src/loraconf.h src/loraconf.h
platformio.ini

View File

@ -9,6 +9,7 @@
; http://docs.platformio.org/page/projectconf.html ; http://docs.platformio.org/page/projectconf.html
; ---> SELECT TARGET PLATFORM HERE! <--- ; ---> SELECT TARGET PLATFORM HERE! <---
[platformio] [platformio]
;env_default = generic ;env_default = generic
@ -29,20 +30,21 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng
[bintray] [bintray]
user = cyberman54 user = cyberman54
repository = paxcounter repository = paxcounter-firmware
package = esp32-paxcounter package = ttgov21_old
api_token = *** api_token = 2e10f923df5d47b9c7e25752510322a1d65ee997
[wifi] [wifi]
ssid = *** ssid = testnet
password = *** password = test0815
[common] [common]
platform = https://github.com/platformio/platform-espressif32.git platform = https://github.com/platformio/platform-espressif32.git
; firmware version, please modify it between releases ; firmware version, please modify it between releases
; positive integer value ; positive integer value
release_version = 4 ;release_version = 1.4.30
release_version = 3
; build configuration based on Bintray and Wi-Fi settings ; build configuration based on Bintray and Wi-Fi settings
build_flags = build_flags =
@ -154,6 +156,8 @@ lib_deps =
build_flags = build_flags =
${common.build_flags} ${common.build_flags}
${common_env_data.build_flags} ${common_env_data.build_flags}
;upload_protocol = custom
;extra_scripts = pre:publish_firmware.py
[env:ttgobeam] [env:ttgobeam]
platform = ${common_env_data.platform_espressif32} platform = ${common_env_data.platform_espressif32}

71
publish_firmware.py Normal file
View File

@ -0,0 +1,71 @@
# Copyright (c) 2014-present PlatformIO <contact@platformio.org>
#
# 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
)

View File

@ -1,79 +1,226 @@
#include "OTA.h" /*
Parts of this code:
Copyright (c) 2014-present PlatformIO <contact@platformio.org>
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 <WiFiClientSecure.h>
#include <Update.h>
#include <BintrayClient.h>
#include "ota.h"
const BintrayClient bintray(BINTRAY_USER, BINTRAY_REPO, BINTRAY_PACKAGE); 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) { // Connection timeout
switch (event->event_id) { const uint32_t RESPONSE_TIMEOUT_MS = 5000;
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) { // 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 void start_ota_update() {
// nvs_flash_init(); ota_update = false; // clear ota trigger switch
tcpip_adapter_init();
tcpip_adapter_set_hostname(tcpip_if, PROGNAME);
tcpip_adapter_dhcpc_start(tcpip_if);
// initialize the wifi event handler ESP_LOGI(TAG, "Stopping Wifi scanner");
ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); vTaskDelete(WifiLoopTask);
ESP_LOGI(TAG, "Starting Wifi OTA update");
// switch off monitor more // switch off monitor more
ESP_ERROR_CHECK( ESP_ERROR_CHECK(
esp_wifi_set_promiscuous(false)); // now switch on monitor mode esp_wifi_set_promiscuous(false)); // now switch on monitor mode
ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(NULL)); ESP_ERROR_CHECK(esp_wifi_set_promiscuous_rx_cb(NULL));
wifi_sta_config_t cfg; WiFi.begin(WIFI_SSID, WIFI_PASS);
strcpy((char *)cfg.ssid, WIFI_SSID);
strcpy((char *)cfg.password, WIFI_PASS);
cfg.bssid_set = false;
wifi_config_t sta_cfg; while (WiFi.status() != WL_CONNECTED) {
sta_cfg.sta = cfg; delay(2000);
ESP_LOGI(TAG, "trying to connect to %s", WIFI_SSID);
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, "connected to %s", WIFI_SSID);
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(); checkFirmwareUpdates();
ESP.restart(); // reached if update was not successful 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);
}
// 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.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();
}
} }

View File

@ -2,12 +2,12 @@
#define OTA_H #define OTA_H
#include <Arduino.h> #include <Arduino.h>
#include "globals.h"
#include <BintrayClient.h>
#include <WiFi.h> #include <WiFi.h>
#include "ota.h" #include "globals.h"
#include "SecureOTA.h" #include "wifiscan.h"
void checkFirmwareUpdates();
void processOTAUpdate(const String &version);
void start_ota_update(); void start_ota_update();
#endif // OTA_H #endif // OTA_H

View File

@ -1,225 +0,0 @@
/*
Copyright (c) 2014-present PlatformIO <contact@platformio.org>
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 <WiFiClientSecure.h>
#include <Update.h>
#include <BintrayClient.h>
#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();
}
}

View File

@ -1,25 +0,0 @@
/*
Copyright (c) 2014-present PlatformIO <contact@platformio.org>
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 <Arduino.h>
void checkFirmwareUpdates();
void processOTAUpdate(const String &version);
#endif // SECURE_OTA_H