1140 lines
34 KiB
C++
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 = "ovms-ota.bit-cloud.de";
|
|
|
|
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 = "ovms-ota.bit-cloud.de";
|
|
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 = "ovms-ota.bit-cloud.de";
|
|
|
|
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;
|
|
}
|