OVMS3/OVMS.V3/components/ovms_ota/src/ovms_ota.cpp

1140 lines
34 KiB
C++

/*
; Project: Open Vehicle Monitor System
; Date: 14th March 2017
;
; Changes:
; 1.0 Initial release
;
; (C) 2011 Michael Stegen / Stegen Electronics
; (C) 2011-2017 Mark Webb-Johnson
; (C) 2011 Sonny Chen @ EPRO/DX
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
; THE SOFTWARE.
*/
#include "ovms_log.h"
static const char *TAG = "ota";
#include <stdlib.h>
#include <stdio.h>
#include <sys/stat.h>
#include <string>
#include <string.h>
#include <esp_system.h>
#include <esp_ota_ops.h>
#include "strverscmp.h"
#include "ovms_ota.h"
#include "ovms_command.h"
#include "ovms_boot.h"
#include "ovms_config.h"
#include "ovms_metrics.h"
#include "ovms_peripherals.h"
#include "ovms_notify.h"
#include "metrics_standard.h"
#include "ovms_http.h"
#include "ovms_buffer.h"
#include "ovms_boot.h"
#include "ovms_netmanager.h"
#include "ovms_version.h"
#include "crypt_md5.h"
OvmsOTA MyOTA __attribute__ ((init_priority (4400)));
////////////////////////////////////////////////////////////////////////////////
// Utility functions
int buildverscmp(std::string v1, std::string v2)
{
// compare canonical versions & check dirty state:
// - cut off "-dirty[…]" and/or "/<partition>/<tag>[…]"
// - if versions are equal and one of them is dirty, always return 1 (→update)
int dirtycnt = 0;
auto canonicalize = [&dirtycnt](std::string &v)
{
std::string::size_type p;
if ((p = v.find("-dirty")) != std::string::npos)
{
v.resize(p);
dirtycnt++;
}
else if ((p = v.find_first_of('/')) != std::string::npos)
v.resize(p);
};
canonicalize(v1);
canonicalize(v2);
int cmp = strverscmp(v1.c_str(), v2.c_str());
if (cmp == 0 && dirtycnt == 1)
return 1;
return cmp;
}
////////////////////////////////////////////////////////////////////////////////
// Commands
void ota_status(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
ota_info info;
std::string version;
int len = 0;
bool check_update = (strcmp(cmd->GetName(), "status")==0);
MyOTA.GetStatus(info, check_update);
if (info.hardware_info != "")
len += writer->printf("Hardware: %s\n", info.hardware_info.c_str());
if (info.version_firmware != "")
len += writer->printf("Firmware: %s\n", info.version_firmware.c_str());
if (info.partition_running != "")
len += writer->printf("Running partition: %s\n", info.partition_running.c_str());
if (info.partition_boot != "")
len += writer->printf("Boot partition: %s\n", info.partition_boot.c_str());
if (MyOTA.IsFlashStatus())
{
if (MyOTA.GetFlashPerc()>0)
len += writer->printf("Status: %s (%d%%)\n", MyOTA.GetFlashStatus(), MyOTA.GetFlashPerc());
else
len += writer->printf("Status: %s\n", MyOTA.GetFlashStatus());
}
version = GetOVMSPartitionVersion(ESP_PARTITION_SUBTYPE_APP_FACTORY);
if (version != "")
len += writer->printf("Factory image: %s\n", version.c_str());
version = GetOVMSPartitionVersion(ESP_PARTITION_SUBTYPE_APP_OTA_0);
if (version != "")
len += writer->printf("OTA_O image: %s\n", version.c_str());
version = GetOVMSPartitionVersion(ESP_PARTITION_SUBTYPE_APP_OTA_1);
if (version != "")
len += writer->printf("OTA_1 image: %s\n", version.c_str());
if (info.version_server != "")
{
len += writer->printf("Server Available: %s%s\n", info.version_server.c_str(),
(buildverscmp(info.version_server,info.version_firmware) > 0) ? " (is newer)" : " (no update required)");
if (!info.changelog_server.empty())
{
writer->puts("");
writer->puts(info.changelog_server.c_str());
}
}
if (len == 0)
writer->puts("OTA status unknown");
}
void ota_flash_vfs(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *target = esp_ota_get_next_update_partition(running);
OvmsMutexLock m_lock(&MyOTA.m_flashing,0);
if (!m_lock.IsLocked())
{
writer->puts("Error: Flash operation already in progress - cannot flash again");
return;
}
if (running==NULL)
{
writer->puts("Error: Current running image cannot be determined - aborting");
return;
}
writer->printf("Current running partition is: %s\n",running->label);
if (target==NULL)
{
writer->puts("Error: Target partition cannot be determined - aborting");
return;
}
writer->printf("Target partition is: %s\n",target->label);
if (running == target)
{
writer->puts("Error: Cannot flash to running image partition");
return;
}
if (MyConfig.ProtectedPath(argv[0]))
{
writer->puts("Error: protected path");
return;
}
struct stat ds;
if (stat(argv[0], &ds) != 0)
{
writer->printf("Error: Cannot find file %s\n",argv[0]);
return;
}
writer->printf("Source image is %d bytes in size\n",ds.st_size);
FILE* f = fopen(argv[0], "r");
if (f == NULL)
{
writer->printf("Error: Cannot open %s\n",argv[0]);
return;
}
MyOTA.SetFlashStatus("OTA Flash VFS: Preparing flash partition...");
writer->puts(MyOTA.GetFlashStatus());
esp_ota_handle_t otah;
esp_err_t err = esp_ota_begin(target, ds.st_size, &otah);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d when starting OTA operation\n",err);
fclose(f);
return;
}
MyOTA.SetFlashStatus("OTA Flash VFS: Flashing image partition...");
writer->puts(MyOTA.GetFlashStatus());
char buf[512];
size_t done = 0;
while(size_t n = fread(buf, sizeof(char), sizeof(buf), f))
{
err = esp_ota_write(otah, buf, n);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d when writing to flash - state is inconsistent\n",err);
esp_ota_end(otah);
fclose(f);
return;
}
done += n;
MyOTA.SetFlashPerc((done*100)/ds.st_size);
}
fclose(f);
MyOTA.SetFlashStatus("OTA Flash VFS: Finalising flash write");
err = esp_ota_end(otah);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d finalising OTA operation - state is inconsistent\n",err);
return;
}
fclose(f);
MyOTA.SetFlashStatus("OTA Flash VFS: Setting boot partition...");
writer->puts(MyOTA.GetFlashStatus());
err = esp_ota_set_boot_partition(target);
MyOTA.ClearFlashStatus();
if (err != ESP_OK)
{
writer->printf("Error: ESP32 error #%d setting boot partition - check before rebooting\n",err);
return;
}
writer->printf("OTA flash was successful\n Flashed %d bytes from %s\n Next boot will be from '%s'\n",
ds.st_size,argv[0],target->label);
MyConfig.SetParamValue("ota", "vfs.mru", argv[0]);
}
void ota_flash_http(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string url;
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *target = esp_ota_get_next_update_partition(running);
OvmsMutexLock m_lock(&MyOTA.m_flashing,0);
if (!m_lock.IsLocked())
{
writer->puts("Error: Flash operation already in progress - cannot flash again");
return;
}
if (running==NULL)
{
writer->puts("Error: Current running image cannot be determined - aborting");
return;
}
writer->printf("Current running partition is: %s\n",running->label);
if (target==NULL)
{
writer->puts("Error: Target partition cannot be determined - aborting");
return;
}
writer->printf("Target partition is: %s\n",target->label);
if (running == target)
{
writer->puts("Error: Cannot flash to running image partition");
return;
}
// URL
if (argc == 0)
{
// Automatically build the URL based on firmware
std::string tag = MyConfig.GetParamValue("ota","tag");
url = MyConfig.GetParamValue("ota","server");
if (url.empty())
url = "api.openvehicles.com/firmware/ota";
url.append("/");
url.append(GetOVMSProduct());
url.append("/");
if (tag.empty())
url.append(CONFIG_OVMS_VERSION_TAG);
else
url.append(tag);
url.append("/ovms3.bin");
}
else
{
url = argv[0];
}
writer->printf("Download firmware from %s to %s\n",url.c_str(),target->label);
// HTTP client request...
OvmsHttpClient http(url);
if (!http.IsOpen())
{
writer->puts("Error: Request failed");
return;
}
size_t expected = http.BodySize();
if (expected < 32)
{
writer->printf("Error: Expected download file size (%d) is invalid\n",expected);
return;
}
writer->printf("Expected file size is %d\n",expected);
MyOTA.SetFlashStatus("OTA Flash HTTP: Preparing flash partition...");
writer->puts(MyOTA.GetFlashStatus());
esp_ota_handle_t otah;
esp_err_t err = esp_ota_begin(target, expected, &otah);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d when starting OTA operation\n",err);
http.Disconnect();
return;
}
// Now, process the body
uint8_t rbuf[512];
size_t filesize = 0;
size_t sofar = 0;
MyOTA.SetFlashStatus("OTA Flash HTTP: Downloading OTA image...");
while (int k = http.BodyRead(rbuf,512))
{
filesize += k;
sofar += k;
MyOTA.SetFlashPerc((filesize*100)/expected);
if (sofar > 100000)
{
writer->printf("Downloading... (%d bytes so far)\n",filesize);
sofar = 0;
}
if (filesize > target->size)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: Download firmware is bigger than available partition space - state is inconsistent\n");
esp_ota_end(otah);
http.Disconnect();
return;
}
err = esp_ota_write(otah, rbuf, k);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d when writing to flash - state is inconsistent\n",err);
esp_ota_end(otah);
http.Disconnect();
return;
}
}
http.Disconnect();
writer->printf("Download complete (at %d bytes)\n",filesize);
if (filesize != expected)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: Download file size (%d) does not match expected (%d)\n",filesize,expected);
esp_ota_end(otah);
return;
}
MyOTA.SetFlashStatus("OTA Flash HTTP: Finalising flash write");
err = esp_ota_end(otah);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d finalising OTA operation - state is inconsistent\n",err);
return;
}
// All done
MyOTA.SetFlashStatus("OTA Flash HTTP: Setting boot partition...");
writer->puts(MyOTA.GetFlashStatus());
err = esp_ota_set_boot_partition(target);
MyOTA.ClearFlashStatus();
if (err != ESP_OK)
{
writer->printf("Error: ESP32 error #%d setting boot partition - check before rebooting\n",err);
return;
}
writer->printf("OTA flash was successful\n Flashed %d bytes from %s\n Next boot will be from '%s'\n",
http.BodySize(),url.c_str(),target->label);
MyConfig.SetParamValue("ota", "http.mru", url);
}
void ota_flash_auto(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
bool force = (strcmp(cmd->GetName(), "force")==0);
writer->puts("Triggering automatic firmware update...");
MyOTA.LaunchAutoFlash(force ? OTA_FlashCfg_Force : OTA_FlashCfg_Default);
}
void ota_boot(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string tn = cmd->GetName();
OvmsMutexLock m_lock(&MyOTA.m_flashing,0);
if (!m_lock.IsLocked())
{
writer->puts("Error: Flash operation already in progress - cannot flash again");
return;
}
esp_partition_subtype_t subtype = ESP_PARTITION_SUBTYPE_ANY;
if (tn.compare("factory")==0)
{ subtype = ESP_PARTITION_SUBTYPE_APP_FACTORY; }
else if (tn.compare("ota_0")==0)
{ subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0; }
else if (tn.compare("ota_1")==0)
{ subtype = ESP_PARTITION_SUBTYPE_APP_OTA_1; }
if (subtype == ESP_PARTITION_SUBTYPE_ANY) return;
const esp_partition_t* p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, subtype, NULL);
if (p != NULL)
{
esp_err_t err = esp_ota_set_boot_partition(p);
switch (err)
{
case ESP_OK:
writer->printf("Boot from %s at 0x%08x (size 0x%08x)\n",p->label,p->address,p->size);
break;
case ESP_ERR_INVALID_ARG:
writer->puts("Error: partition argument didn't point to a valid OTA partition of type 'app'");
break;
case ESP_ERR_OTA_VALIDATE_FAILED:
writer->puts("Error: partition contained invalid app image, or signature validation failed");
break;
case ESP_ERR_NOT_FOUND:
writer->puts("Error: OTA partition not found");
break;
case ESP_ERR_FLASH_OP_TIMEOUT:
case ESP_ERR_FLASH_OP_FAIL:
writer->puts("Error: Flash erase/write failed");
break;
default:
writer->printf("Error: cannot set OTA boot partition (error %d)\n",err);
break;
}
}
}
void ota_erase(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string tn = cmd->GetName();
OvmsMutexLock m_lock(&MyOTA.m_flashing,0);
if (!m_lock.IsLocked())
{
writer->puts("Error: Flash operation already in progress - cannot flash again");
return;
}
esp_partition_subtype_t subtype = ESP_PARTITION_SUBTYPE_ANY;
if (tn.compare("factory")==0)
{ subtype = ESP_PARTITION_SUBTYPE_APP_FACTORY; }
else if (tn.compare("ota_0")==0)
{ subtype = ESP_PARTITION_SUBTYPE_APP_OTA_0; }
else if (tn.compare("ota_1")==0)
{ subtype = ESP_PARTITION_SUBTYPE_APP_OTA_1; }
if (subtype == ESP_PARTITION_SUBTYPE_ANY) return;
const esp_partition_t *p = esp_ota_get_running_partition();
if ((p != NULL) && (p->subtype == subtype))
{
writer->puts("Error: Cannot erase currently running partition");
return;
}
p = esp_ota_get_boot_partition();
if ((p != NULL) && (p->subtype == subtype))
{
writer->puts("Error: Cannot erase boot partition");
return;
}
p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, subtype, NULL);
if (p != NULL)
{
MyOTA.SetFlashStatus("OTA Erase: Erasing partition...");
writer->puts(MyOTA.GetFlashStatus());
esp_ota_handle_t otah;
esp_err_t err = esp_ota_begin(p, OTA_SIZE_UNKNOWN, &otah);
MyOTA.ClearFlashStatus();
if (err != ESP_OK)
{
writer->printf("Error: ESP32 error #%d starting OTA operation\n",err);
return;
}
esp_ota_end(otah);
writer->puts("Partition erase complete");
}
else
{
writer->puts("Error: Cannot find specified partition");
}
}
void ota_copy(int verbosity, OvmsWriter* writer, OvmsCommand* cmd, int argc, const char* const* argv)
{
std::string fn = cmd->GetParent()->GetName();
std::string tn = cmd->GetName();
OvmsMutexLock m_lock(&MyOTA.m_flashing,0);
if (!m_lock.IsLocked())
{
writer->puts("Error: Flash operation already in progress - cannot flash again");
return;
}
esp_partition_subtype_t from = ESP_PARTITION_SUBTYPE_ANY;
if (fn.compare("factory")==0)
{ from = ESP_PARTITION_SUBTYPE_APP_FACTORY; }
else if (fn.compare("ota_0")==0)
{ from = ESP_PARTITION_SUBTYPE_APP_OTA_0; }
else if (fn.compare("ota_1")==0)
{ from = ESP_PARTITION_SUBTYPE_APP_OTA_1; }
else return;
esp_partition_subtype_t to = ESP_PARTITION_SUBTYPE_ANY;
if (tn.compare("factory")==0)
{ to = ESP_PARTITION_SUBTYPE_APP_FACTORY; }
else if (tn.compare("ota_0")==0)
{ to = ESP_PARTITION_SUBTYPE_APP_OTA_0; }
else if (tn.compare("ota_1")==0)
{ to = ESP_PARTITION_SUBTYPE_APP_OTA_1; }
else return;
const esp_partition_t *p = esp_ota_get_running_partition();
if ((p != NULL) && (p->subtype == to))
{
writer->puts("Error: Cannot copy to currently running partition");
return;
}
p = esp_ota_get_boot_partition();
if ((p != NULL) && (p->subtype == to))
{
writer->puts("Error: Cannot copy to boot partition");
return;
}
const esp_partition_t *from_p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, from, NULL);
if (from_p == NULL)
{
writer->puts("Error: Could not find partition to copy from");
return;
}
const esp_partition_t *to_p = esp_partition_find_first(ESP_PARTITION_TYPE_APP, to, NULL);
if (to_p == NULL)
{
writer->puts("Error: Could not find partition to copy to");
return;
}
if (from_p->size != to_p->size)
{
writer->puts("Error: The two specified partitions are not the same size");
return;
}
writer->printf("OTA copy %s (%08x) -> %s (%08x) size %u\n",
fn.c_str(), from_p->address,
tn.c_str(), to_p->address, to_p->size);
MyOTA.SetFlashStatus("OTA Copy: Preparing flash partition...");
esp_ota_handle_t otah;
esp_err_t err = esp_ota_begin(to_p, OTA_SIZE_UNKNOWN, &otah);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d starting OTA operation\n",err);
return;
}
MyOTA.SetFlashStatus("OTA Copy: Copying flash image...");
size_t offset = 0;
while (offset < to_p->size)
{
char buf[512];
size_t todo = to_p->size - offset;
if (todo > sizeof(buf)) todo=sizeof(buf);
esp_err_t err = esp_partition_read(from_p, offset, buf, todo);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d reading source at offset %d",err,offset);
esp_ota_end(otah);
return;
}
err = esp_ota_write(otah, buf, todo);
if (err != ESP_OK)
{
MyOTA.ClearFlashStatus();
writer->printf("Error: ESP32 error #%d writing destinatio at offset %d",err,offset);
esp_ota_end(otah);
return;
}
offset += todo;
MyOTA.SetFlashPerc((offset*100)/to_p->size);
}
MyOTA.SetFlashStatus("OTA Copy: Finalising copy...");
esp_ota_end(otah);
MyOTA.ClearFlashStatus();
writer->puts("OTA copy complete");
}
////////////////////////////////////////////////////////////////////////////////
// OvmsOTA
//
// The main OTA functionality
#ifdef CONFIG_OVMS_COMP_SDCARD
void OvmsOTA::CheckFlashSD(std::string event, void* data)
{
if (path_exists("/sd/ovms3.bin"))
{
LaunchAutoFlash(OTA_FlashCfg_FromSD);
}
}
bool OvmsOTA::AutoFlashSD()
{
FILE* f = fopen("/sd/ovms3.bin", "r");
if (f == NULL) return false;
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *target = esp_ota_get_next_update_partition(running);
OvmsMutexLock m_lock(&MyOTA.m_flashing,0);
if (!m_lock.IsLocked())
{
ESP_LOGW(TAG, "AutoFlashSD: Flash operation already in progress - cannot auto flash");
fclose(f);
return false;
}
if (running==NULL)
{
ESP_LOGE(TAG, "AutoFlashSD Error: Current running image cannot be determined - aborting");
fclose(f);
return false;
}
ESP_LOGW(TAG, "AutoFlashSD Current running partition is: %s",running->label);
if (target==NULL)
{
ESP_LOGE(TAG, "AutoFlashSD Error: Target partition cannot be determined - aborting");
fclose(f);
return false;
}
ESP_LOGW(TAG, "AutoFlashSD Target partition is: %s",target->label);
if (running == target)
{
ESP_LOGE(TAG, "AutoFlashSD Error: Cannot flash to running image partition");
fclose(f);
return false;
}
struct stat ds;
if (stat("/sd/ovms3.bin", &ds) != 0)
{
ESP_LOGE(TAG, "AutoFlashSD Error: Cannot stat file");
fclose(f);
return false;
}
ESP_LOGW(TAG, "AutoFlashSD Source image is %d bytes in size",(int)ds.st_size);
SetFlashStatus("OTA Auto Flash SD: Preparing flash partition...",0,true);
esp_ota_handle_t otah;
esp_err_t err = esp_ota_begin(target, ds.st_size, &otah);
if (err != ESP_OK)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlashSD Error: ESP32 error #%d when starting OTA operation",err);
fclose(f);
return false;
}
SetFlashStatus("OTA Auto Flash SD: Flashing image paritition...",0,true);
char buf[512];
size_t done = 0;
while(size_t n = fread(buf, sizeof(char), sizeof(buf), f))
{
err = esp_ota_write(otah, buf, n);
if (err != ESP_OK)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlashSD Error: ESP32 error #%d when writing to flash - state is inconsistent",err);
esp_ota_end(otah);
fclose(f);
return false;
}
done += n;
SetFlashPerc((done*100)/ds.st_size);
}
fclose(f);
SetFlashStatus("OTA Auto Flash SD: Finalising flash image...",0,true);
err = esp_ota_end(otah);
if (err != ESP_OK)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlashSD Error: ESP32 error #%d finalising OTA operation - state is inconsistent",err);
return false;
}
SetFlashStatus("OTA Auto Flash SD: Setting boot partition...",0,true);
err = esp_ota_set_boot_partition(target);
ClearFlashStatus();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "AutoFlashSD Error: ESP32 error #%d setting boot partition - check before rebooting",err);
return false;
}
remove("/sd/ovms3.done"); // Ensure the target is removed first
if (rename("/sd/ovms3.bin","/sd/ovms3.done") != 0)
{
ESP_LOGE(TAG, "AutoFlashSD Error: ovms3.bin could not be renamed to ovms3.done - check before rebooting");
return false;
}
ESP_LOGW(TAG, "AutoFlashSD OTA flash successful: Flashed %d bytes, and booting from '%s'",
(int)ds.st_size,target->label);
return true;
}
#endif // #ifdef CONFIG_OVMS_COMP_SDCARD
OvmsOTA::OvmsOTA()
{
ESP_LOGI(TAG, "Initialising OTA (4400)");
m_autotask = NULL;
m_lastcheckday = -1;
m_flashstatus = NULL;
m_flashperc = 0;
MyConfig.RegisterParam("ota", "OTA setup and status", true, true);
#undef bind // Kludgy, but works
using std::placeholders::_1;
using std::placeholders::_2;
MyEvents.RegisterEvent(TAG,"ticker.600", std::bind(&OvmsOTA::Ticker600, this, _1, _2));
#ifdef CONFIG_OVMS_COMP_SDCARD
MyEvents.RegisterEvent(TAG,"sd.mounted", std::bind(&OvmsOTA::CheckFlashSD, this, _1, _2));
#endif // #ifdef CONFIG_OVMS_COMP_SDCARD
OvmsCommand* cmd_ota = MyCommandApp.RegisterCommand("ota","OTA framework", ota_status, "", 0, 0, false);
OvmsCommand* cmd_otastatus = cmd_ota->RegisterCommand("status","Show OTA status",ota_status);
cmd_otastatus->RegisterCommand("nocheck","…skip check for available update",ota_status);
OvmsCommand* cmd_otaflash = cmd_ota->RegisterCommand("flash","OTA flash");
cmd_otaflash->RegisterCommand("vfs","OTA flash vfs",ota_flash_vfs,"<file>",1,1);
cmd_otaflash->RegisterCommand("http","OTA flash http",ota_flash_http,"[<url>]",0,1);
OvmsCommand* cmd_otaflash_auto = cmd_otaflash->RegisterCommand("auto","Automatic regular OTA flash (over web)",ota_flash_auto);
cmd_otaflash_auto->RegisterCommand("force","…force update (even if server version older)",ota_flash_auto);
OvmsCommand* cmd_otaboot = cmd_ota->RegisterCommand("boot","OTA boot");
cmd_otaboot->RegisterCommand("factory","Boot from factory image",ota_boot);
cmd_otaboot->RegisterCommand("ota_0","Boot from ota_0 image",ota_boot);
cmd_otaboot->RegisterCommand("ota_1","Boot from ota_1 image",ota_boot);
OvmsCommand* cmd_otaerase = cmd_ota->RegisterCommand("erase","OTA erase");
cmd_otaerase->RegisterCommand("factory","Erase factory image",ota_erase);
cmd_otaerase->RegisterCommand("ota_0","Erase ota_0 image",ota_erase);
cmd_otaerase->RegisterCommand("ota_1","Erase ota_1 image",ota_erase);
OvmsCommand* cmd_otacopy = cmd_ota->RegisterCommand("copy","OTA copy");
OvmsCommand* cmd_otacopyf = cmd_otacopy->RegisterCommand("factory","OTA copy factory <to>");
cmd_otacopyf->RegisterCommand("ota_0","Copy factory to ota_0 image",ota_copy);
cmd_otacopyf->RegisterCommand("ota_1","Copy factory to ota_1 image",ota_copy);
OvmsCommand* cmd_otacopy0 = cmd_otacopy->RegisterCommand("ota_0","OTA copy ota_0 <to>");
cmd_otacopy0->RegisterCommand("factory","Copy ota_0 to factory image",ota_copy);
cmd_otacopy0->RegisterCommand("ota_1","Copy ota_0 to ota_1 image",ota_copy);
OvmsCommand* cmd_otacopy1 = cmd_otacopy->RegisterCommand("ota_1","OTA copy ota_1 <to>");
cmd_otacopy1->RegisterCommand("factory","Copy ota_1 to factory image",ota_copy);
cmd_otacopy1->RegisterCommand("ota_0","Copy ota_1 to ota_0 image",ota_copy);
}
OvmsOTA::~OvmsOTA()
{
}
void OvmsOTA::GetStatus(ota_info& info, bool check_update /*=true*/)
{
info.hardware_info = "";
info.version_firmware = "";
info.version_server = "";
info.update_available = false;
info.partition_running = "";
info.partition_boot = "";
info.changelog_server = "";
if (StdMetrics.ms_m_hardware)
info.hardware_info = StdMetrics.ms_m_hardware->AsString();
OvmsMetricString* m = StandardMetrics.ms_m_version;
if (m != NULL)
{
info.version_firmware = m->AsString();
if (check_update && MyNetManager.m_connected_any)
{
// We have a wifi connection, so let's try to find out the version the server has
std::string tag = MyConfig.GetParamValue("ota","tag");
std::string url = MyConfig.GetParamValue("ota","server");
if (url.empty())
url = "api.openvehicles.com/firmware/ota";
url.append("/");
url.append(GetOVMSProduct());
url.append("/");
if (tag.empty())
url.append(CONFIG_OVMS_VERSION_TAG);
else
url.append(tag);
url.append("/ovms3.ver");
OvmsHttpClient http(url);
if (http.IsOpen() && http.ResponseCode() == 200 && http.BodyHasLine())
{
info.version_server = http.BodyReadLine();
char rbuf[512];
while (size_t k = http.BodyRead(rbuf,512))
{
info.changelog_server.append(rbuf,k);
}
http.Disconnect();
}
}
if (buildverscmp(info.version_server, info.version_firmware) > 0)
info.update_available = true;
const esp_partition_t *p = esp_ota_get_running_partition();
if (p != NULL)
{
info.partition_running = p->label;
}
p = esp_ota_get_boot_partition();
if (p != NULL)
{
info.partition_boot = p->label;
}
}
}
void OvmsOTA::Ticker600(std::string event, void* data)
{
if (MyConfig.GetParamValueBool("auto", "ota", true) == false)
return;
time_t rawtime;
time ( &rawtime );
struct tm* tmu = localtime(&rawtime);
if ((tmu->tm_hour == MyConfig.GetParamValueInt("ota","auto.hour",2)) &&
(tmu->tm_mday != m_lastcheckday))
{
m_lastcheckday = tmu->tm_mday; // So we only try once a day (unless cleared due to a temporary fault)
LaunchAutoFlash(OTA_FlashCfg_Default);
}
}
bool OvmsOTA::IsFlashStatus()
{
return (m_flashstatus != NULL);
}
void OvmsOTA::SetFlashStatus(const char* status, int perc, bool dolog)
{
m_flashstatus = status;
m_flashperc = perc;
if (dolog)
{ ESP_LOGI(TAG, "%s", status); }
}
void OvmsOTA::SetFlashPerc(int perc)
{
m_flashperc = perc;
}
void OvmsOTA::ClearFlashStatus()
{
m_flashstatus = NULL;
m_flashperc = 0;
}
const char* OvmsOTA::GetFlashStatus()
{
return m_flashstatus;
}
int OvmsOTA::GetFlashPerc()
{
return m_flashperc;
}
static void OTAFlashTask(void *pvParameters)
{
ota_flashcfg_t cfg = (ota_flashcfg_t)((uint32_t)pvParameters);
bool force = (cfg == OTA_FlashCfg_Force);
bool fromsd = (cfg == OTA_FlashCfg_FromSD);
bool success;
ESP_LOGI(TAG, "AutoFlash %s: Task is running%s", (fromsd)?"SD":"OTA", (force)?" forced":"");
if (fromsd)
{
success = MyOTA.AutoFlashSD();
}
else
{
success = MyOTA.AutoFlash(force);
}
if (success)
{
// Flash has completed. We now need to reboot
vTaskDelay(pdMS_TO_TICKS(5000));
// All done. Let's restart...
ESP_LOGI(TAG, "AutoFlash %s: Task complete. Requesting restart...", (fromsd)?"SD":"OTA");
MyBoot.Restart();
MyBoot.SetFirmwareUpdate();
}
MyOTA.m_autotask = NULL;
vTaskDelete(NULL);
}
void OvmsOTA::LaunchAutoFlash(ota_flashcfg_t cfg /*=OTA_FlashCfg_Default*/)
{
if (m_autotask != NULL)
{
ESP_LOGW(TAG, "AutoFlash: Task already running (cannot launch new)");
return;
}
xTaskCreatePinnedToCore(OTAFlashTask, "OVMS AutoFlash",
6144, (void*)cfg, 5, &m_autotask, CORE(1));
}
bool OvmsOTA::AutoFlash(bool force)
{
const esp_partition_t *running = esp_ota_get_running_partition();
const esp_partition_t *target = esp_ota_get_next_update_partition(running);
if (running==NULL)
{
ESP_LOGW(TAG, "AutoFlash: Current running image cannot be determined - aborting");
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
if (target==NULL)
{
ESP_LOGW(TAG, "AutoFlash: Target partition cannot be determined - aborting");
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
if (running == target)
{
ESP_LOGW(TAG, "AutoFlash: Cannot flash to running image partition");
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
ota_info info;
MyOTA.GetStatus(info, true);
if (info.version_server.length() == 0)
{
ESP_LOGW(TAG,"AutoFlash: Server version could not be determined");
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
if ((!force) && (buildverscmp(info.version_server,info.version_firmware) <= 0))
{
ESP_LOGI(TAG,"AutoFlash: No new firmware available (%s is current)",info.version_server.c_str());
return false;
}
if ((!MyNetManager.m_connected_wifi) && (!MyConfig.GetParamValueBool("ota", "auto.allow.modem", false)))
{
ESP_LOGW(TAG, "AutoFlash: Cannot flash without wifi connection, or auto.allow.modem");
if (info.version_server.compare(m_lastnotifyversion) != 0)
{
MyNotify.NotifyStringf("info", "ota.update", "New firmware %s is available. Connect to WIFI to update", info.version_server.c_str());
m_lastnotifyversion = info.version_server;
}
return false;
}
OvmsMutexLock m_lock(&m_flashing,0);
if (!m_lock.IsLocked())
{
ESP_LOGW(TAG, "AutoFlash: Flash operation already in progress - cannot auto flash");
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
std::string tag = MyConfig.GetParamValue("ota","tag");
std::string url = MyConfig.GetParamValue("ota","server");
if (url.empty())
url = "api.openvehicles.com/firmware/ota";
url.append("/");
url.append(GetOVMSProduct());
url.append("/");
if (tag.empty())
url.append(CONFIG_OVMS_VERSION_TAG);
else
url.append(tag);
url.append("/ovms3.bin");
ESP_LOGI(TAG, "AutoFlash: Update %s to %s (%s)",
target->label,
info.version_server.c_str(),
url.c_str());
MyNotify.NotifyStringf("info", "ota.update", "New OTA firmware %s is now being downloaded", info.version_server.c_str());
// HTTP client request...
OvmsHttpClient http(url);
if (!http.IsOpen())
{
ESP_LOGE(TAG, "AutoFlash: http://%s request failed", url.c_str());
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
size_t expected = http.BodySize();
if (expected < 32)
{
ESP_LOGE(TAG, "AutoFlash: Expected download file size (%d) is invalid", expected);
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
SetFlashStatus("OTA Auto Flash: Preparing flash partition...",0,true);
esp_ota_handle_t otah;
esp_err_t err = esp_ota_begin(target, expected, &otah);
if (err != ESP_OK)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlash: ESP32 error #%d when starting OTA operation", err);
http.Disconnect();
return false;
}
// Now, process the body
SetFlashStatus("OTA Auto Flash: Downloading OTA image...");
uint8_t rbuf[512];
size_t filesize = 0;
while (int k = http.BodyRead(rbuf,512))
{
filesize += k;
SetFlashPerc((filesize*100)/expected);
if (filesize > target->size)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlash: Download firmware is bigger than available partition space - state is inconsistent");
esp_ota_end(otah);
http.Disconnect();
return false;
}
err = esp_ota_write(otah, rbuf, k);
if (err != ESP_OK)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlash: ESP32 error #%d when writing to flash - state is inconsistent", err);
esp_ota_end(otah);
http.Disconnect();
return false;
}
}
http.Disconnect();
ESP_LOGI(TAG, "AutoFlash:: Download complete (at %d bytes)", filesize);
if (filesize != expected)
{
ClearFlashStatus();
ESP_LOGE(TAG, "AutoFlash: Download file size (%d) does not match expected (%d)", filesize, expected);
esp_ota_end(otah);
m_lastcheckday = -1; // Allow to try again within the same day
return false;
}
SetFlashStatus("OTA Auto Flash: Finalising flash partition...");
err = esp_ota_end(otah);
ClearFlashStatus();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "AutoFlash: ESP32 error #%d finalising OTA operation - state is inconsistent", err);
return false;
}
// All done
ESP_LOGI(TAG, "AutoFlash: Setting boot partition...");
err = esp_ota_set_boot_partition(target);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "AutoFlash: ESP32 error #%d setting boot partition - check before rebooting", err);
return false;
}
ESP_LOGI(TAG, "AutoFlash: Success flash of %d bytes from %s", http.BodySize(), url.c_str());
MyNotify.NotifyStringf("info", "ota.update", "OTA firmware %s has been updated (OVMS will restart)", info.version_server.c_str());
MyConfig.SetParamValue("ota", "http.mru", url);
return true;
}