[core] Add Update library
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
* [ssl/MbedTLSClient](ltapi/class_mbed_t_l_s_client.md)
|
* [ssl/MbedTLSClient](ltapi/class_mbed_t_l_s_client.md)
|
||||||
* [IPv6Address](ltapi/classarduino_1_1_i_pv6_address.md)
|
* [IPv6Address](ltapi/classarduino_1_1_i_pv6_address.md)
|
||||||
* [LwIPRxBuffer](ltapi/class_lw_i_p_rx_buffer.md)
|
* [LwIPRxBuffer](ltapi/class_lw_i_p_rx_buffer.md)
|
||||||
|
* [Update](ltapi/class_update.md)
|
||||||
* [WebServer](ltapi/class_web_server.md)
|
* [WebServer](ltapi/class_web_server.md)
|
||||||
* [WiFiMulti](ltapi/class_wi_fi_multi.md)
|
* [WiFiMulti](ltapi/class_wi_fi_multi.md)
|
||||||
* [Third party libraries](docs/libs-3rd-party.md)
|
* [Third party libraries](docs/libs-3rd-party.md)
|
||||||
|
|||||||
199
arduino/libretuya/libraries/Update/Update.cpp
Normal file
199
arduino/libretuya/libraries/Update/Update.cpp
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/* Copyright (c) Kuba Szczodrzyński 2022-05-29. */
|
||||||
|
|
||||||
|
#include "Update.h"
|
||||||
|
|
||||||
|
UpdateClass::UpdateClass() : ctx(NULL), info(NULL), buf(NULL) {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the update process.
|
||||||
|
*
|
||||||
|
* @param size total UF2 file size
|
||||||
|
* @param command must be U_FLASH
|
||||||
|
* @return false if parameters are invalid or update is running, true otherwise
|
||||||
|
*/
|
||||||
|
bool UpdateClass::begin(size_t size, int command, int unused2, uint8_t unused3, const char *unused4) {
|
||||||
|
if (ctx)
|
||||||
|
return false;
|
||||||
|
cleanup();
|
||||||
|
|
||||||
|
ctx = uf2_ctx_init(LT.otaGetTarget(), FAMILY);
|
||||||
|
info = uf2_info_init();
|
||||||
|
|
||||||
|
if (!size)
|
||||||
|
return errorArd(UPDATE_ERROR_SIZE);
|
||||||
|
|
||||||
|
if (command != U_FLASH)
|
||||||
|
return errorArd(UPDATE_ERROR_BAD_ARGUMENT);
|
||||||
|
|
||||||
|
bytesTotal = size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finalize the update process. Check for errors and update completion, then activate the new firmware image.
|
||||||
|
*
|
||||||
|
* @param evenIfRemaining no idea
|
||||||
|
* @return false in case of errors or no update running, true otherwise
|
||||||
|
*/
|
||||||
|
bool UpdateClass::end(bool evenIfRemaining) {
|
||||||
|
if (hasError() || !ctx)
|
||||||
|
// false if not running
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!isFinished() && !evenIfRemaining) {
|
||||||
|
// abort if not finished
|
||||||
|
return errorArd(UPDATE_ERROR_ABORT);
|
||||||
|
}
|
||||||
|
// TODO what is evenIfRemaining for?
|
||||||
|
if (!LT.otaSwitch())
|
||||||
|
// try to activate the second OTA
|
||||||
|
return errorArd(UPDATE_ERROR_ACTIVATE);
|
||||||
|
|
||||||
|
cleanup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Write a chunk of data to the buffer or flash memory.
|
||||||
|
*
|
||||||
|
* It's advised to write in 512-byte chunks (or its multiples).
|
||||||
|
*
|
||||||
|
* @param data
|
||||||
|
* @param len
|
||||||
|
* @return size_t
|
||||||
|
*/
|
||||||
|
size_t UpdateClass::write(uint8_t *data, size_t len) {
|
||||||
|
size_t written = 0;
|
||||||
|
if (hasError() || !ctx)
|
||||||
|
// 0 if not running
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* while (buf == bufPos && len >= UF2_BLOCK_SIZE) {
|
||||||
|
// buffer empty and entire block is in data
|
||||||
|
if (!tryWriteData(data, UF2_BLOCK_SIZE)) {
|
||||||
|
// returns 0 if data contains an invalid block
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
data += UF2_BLOCK_SIZE;
|
||||||
|
len -= UF2_BLOCK_SIZE;
|
||||||
|
written += UF2_BLOCK_SIZE;
|
||||||
|
} */
|
||||||
|
|
||||||
|
// write until buffer space is available
|
||||||
|
uint16_t toWrite;
|
||||||
|
while (len && (toWrite = min(len, bufLeft()))) {
|
||||||
|
tryWriteData(data, toWrite);
|
||||||
|
if (hasError())
|
||||||
|
// return on errors
|
||||||
|
return written;
|
||||||
|
data += toWrite;
|
||||||
|
len -= toWrite;
|
||||||
|
written += toWrite;
|
||||||
|
}
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UpdateClass::writeStream(Stream &data) {
|
||||||
|
size_t written = 0;
|
||||||
|
if (hasError() || !ctx)
|
||||||
|
// 0 if not running
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint32_t lastData = millis();
|
||||||
|
// loop until the update is complete
|
||||||
|
while (remaining()) {
|
||||||
|
// check stream availability
|
||||||
|
int available = data.available();
|
||||||
|
if (available <= 0) {
|
||||||
|
if (millis() - lastData > UPDATE_TIMEOUT_MS) {
|
||||||
|
// waited for data too long; abort with error
|
||||||
|
errorArd(UPDATE_ERROR_STREAM);
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// available > 0
|
||||||
|
lastData = millis();
|
||||||
|
|
||||||
|
// read data to fit in the remaining buffer space
|
||||||
|
bufAlloc();
|
||||||
|
uint16_t read = data.readBytes(bufPos, bufLeft());
|
||||||
|
bufPos += read;
|
||||||
|
written += read;
|
||||||
|
tryWriteData();
|
||||||
|
if (hasError())
|
||||||
|
// return on errors
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Try to use the buffer as a block to write. In case of UF2 errors,
|
||||||
|
* error codes are set, the update is aborted and 0 is returned
|
||||||
|
*
|
||||||
|
* @param data received data to copy to buffer or NULL if already in buffer
|
||||||
|
* @param len received data length - must be at most bufLeft()
|
||||||
|
* @return size_t "used" data size - 0 or 512
|
||||||
|
*/
|
||||||
|
size_t UpdateClass::tryWriteData(uint8_t *data, size_t len) {
|
||||||
|
uf2_block_t *block = NULL;
|
||||||
|
|
||||||
|
if (len == UF2_BLOCK_SIZE) {
|
||||||
|
// data has a complete block
|
||||||
|
block = (uf2_block_t *)data;
|
||||||
|
} else if (data && len) {
|
||||||
|
// data has a part of a block, copy it to buffer
|
||||||
|
bufAlloc();
|
||||||
|
memcpy(bufPos, data, len);
|
||||||
|
bufPos += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!block && bufSize() == UF2_BLOCK_SIZE) {
|
||||||
|
// use buffer as block (only if not found above)
|
||||||
|
block = (uf2_block_t *)buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// a complete block has been found
|
||||||
|
if (block) {
|
||||||
|
if (errorUf2(uf2_check_block(ctx, block)))
|
||||||
|
// block is invalid
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (errUf2 == UF2_ERR_IGNORE)
|
||||||
|
// treat ignored blocks as valid
|
||||||
|
return UF2_BLOCK_SIZE;
|
||||||
|
|
||||||
|
if (!bytesWritten) {
|
||||||
|
// parse header block to allow retrieving firmware info
|
||||||
|
if (errorUf2(uf2_parse_header(ctx, block, info)))
|
||||||
|
// header is invalid
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (bytesTotal == UPDATE_SIZE_UNKNOWN) {
|
||||||
|
// set total update size from block count info
|
||||||
|
bytesTotal = block->block_count * UF2_BLOCK_SIZE;
|
||||||
|
} else if (bytesTotal != block->block_count * UF2_BLOCK_SIZE) {
|
||||||
|
// given update size does not match the block count
|
||||||
|
return errorArd(UPDATE_ERROR_SIZE);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// write data blocks normally
|
||||||
|
if (errorUf2(uf2_write(ctx, block)))
|
||||||
|
// block writing failed
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment total writing progress
|
||||||
|
bytesWritten += UF2_BLOCK_SIZE;
|
||||||
|
// call progress callback
|
||||||
|
if (callback)
|
||||||
|
callback(bytesWritten, bytesTotal);
|
||||||
|
return UF2_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateClass Update;
|
||||||
150
arduino/libretuya/libraries/Update/Update.h
Normal file
150
arduino/libretuya/libraries/Update/Update.h
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "uf2ota/uf2ota.h"
|
||||||
|
|
||||||
|
// No Error
|
||||||
|
#define UPDATE_ERROR_OK (0)
|
||||||
|
// Flash Write Failed
|
||||||
|
#define UPDATE_ERROR_WRITE (1)
|
||||||
|
// Flash Erase Failed
|
||||||
|
#define UPDATE_ERROR_ERASE (2)
|
||||||
|
// Flash Read Failed
|
||||||
|
#define UPDATE_ERROR_READ (3)
|
||||||
|
// Not Enough Space
|
||||||
|
#define UPDATE_ERROR_SPACE (4)
|
||||||
|
// Bad Size Given
|
||||||
|
#define UPDATE_ERROR_SIZE (5)
|
||||||
|
// Stream Read Timeout
|
||||||
|
#define UPDATE_ERROR_STREAM (6)
|
||||||
|
// MD5 Check Failed
|
||||||
|
#define UPDATE_ERROR_MD5 (7)
|
||||||
|
// Wrong Magic Byte
|
||||||
|
#define UPDATE_ERROR_MAGIC_BYTE (8)
|
||||||
|
// Could Not Activate The Firmware
|
||||||
|
#define UPDATE_ERROR_ACTIVATE (9)
|
||||||
|
// Partition Could Not be Found
|
||||||
|
#define UPDATE_ERROR_NO_PARTITION (10)
|
||||||
|
// Bad Argument
|
||||||
|
#define UPDATE_ERROR_BAD_ARGUMENT (11)
|
||||||
|
// Aborted
|
||||||
|
#define UPDATE_ERROR_ABORT (12)
|
||||||
|
|
||||||
|
#define UPDATE_SIZE_UNKNOWN 0xFFFFFFFF
|
||||||
|
|
||||||
|
#define U_FLASH 0
|
||||||
|
#define U_SPIFFS 100
|
||||||
|
#define U_AUTH 200
|
||||||
|
|
||||||
|
#define ENCRYPTED_BLOCK_SIZE 16
|
||||||
|
|
||||||
|
#define UPDATE_TIMEOUT_MS 30 * 1000
|
||||||
|
|
||||||
|
class UpdateClass {
|
||||||
|
public:
|
||||||
|
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
|
||||||
|
|
||||||
|
public: /* Update.cpp */
|
||||||
|
UpdateClass();
|
||||||
|
bool begin(
|
||||||
|
size_t size = UPDATE_SIZE_UNKNOWN,
|
||||||
|
int command = U_FLASH,
|
||||||
|
int unused2 = -1,
|
||||||
|
uint8_t unused3 = LOW,
|
||||||
|
const char *unused4 = NULL // this is for SPIFFS
|
||||||
|
);
|
||||||
|
bool end(bool evenIfRemaining = false);
|
||||||
|
size_t write(uint8_t *data, size_t len);
|
||||||
|
size_t writeStream(Stream &data);
|
||||||
|
bool canRollBack();
|
||||||
|
bool rollBack();
|
||||||
|
// bool setMD5(const char *expected_md5);
|
||||||
|
|
||||||
|
private: /* Update.cpp */
|
||||||
|
size_t tryWriteData(uint8_t *data = NULL, size_t len = 0);
|
||||||
|
|
||||||
|
public: /* UpdateUtil.cpp */
|
||||||
|
UpdateClass &onProgress(THandlerFunction_Progress callback);
|
||||||
|
void abort();
|
||||||
|
void printError(Print &out);
|
||||||
|
const char *errorString();
|
||||||
|
const char *getFirmwareName();
|
||||||
|
const char *getFirmwareVersion();
|
||||||
|
const char *getLibreTuyaVersion();
|
||||||
|
const char *getBoardName();
|
||||||
|
|
||||||
|
private: /* UpdateUtil.cpp */
|
||||||
|
void cleanup();
|
||||||
|
bool errorUf2(uf2_err_t err);
|
||||||
|
bool errorArd(uint8_t err);
|
||||||
|
void bufAlloc();
|
||||||
|
uint16_t bufLeft();
|
||||||
|
uint16_t bufSize();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// uf2ota context
|
||||||
|
uf2_ota_t *ctx;
|
||||||
|
uf2_info_t *info;
|
||||||
|
// block buffer
|
||||||
|
uint8_t *buf;
|
||||||
|
uint8_t *bufPos;
|
||||||
|
// update progress - multiplies of 512 bytes
|
||||||
|
uint32_t bytesWritten;
|
||||||
|
uint32_t bytesTotal;
|
||||||
|
// errors
|
||||||
|
uf2_err_t errUf2;
|
||||||
|
uint8_t errArd;
|
||||||
|
// progress callback
|
||||||
|
THandlerFunction_Progress callback;
|
||||||
|
// String _target_md5;
|
||||||
|
// MD5Builder _md5;
|
||||||
|
|
||||||
|
public:
|
||||||
|
String md5String(void) {
|
||||||
|
// return _md5.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
void md5(uint8_t *result) {
|
||||||
|
// return _md5.getBytes(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t getError() {
|
||||||
|
return errArd;
|
||||||
|
}
|
||||||
|
|
||||||
|
uf2_err_t getUF2Error() {
|
||||||
|
return errUf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clearError() {
|
||||||
|
errorUf2(UF2_ERR_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasError() {
|
||||||
|
return errArd != UPDATE_ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isRunning() {
|
||||||
|
return ctx != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isFinished() {
|
||||||
|
return bytesWritten == bytesTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size() {
|
||||||
|
return bytesTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t progress() {
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t remaining() {
|
||||||
|
return bytesTotal - bytesWritten;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
extern UpdateClass Update;
|
||||||
162
arduino/libretuya/libraries/Update/UpdateUtil.cpp
Normal file
162
arduino/libretuya/libraries/Update/UpdateUtil.cpp
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/* Copyright (c) Kuba Szczodrzyński 2022-05-30. */
|
||||||
|
|
||||||
|
#include "Update.h"
|
||||||
|
|
||||||
|
static const uint8_t errorMap[] = {
|
||||||
|
UPDATE_ERROR_OK, /* UF2_ERR_OK - no error */
|
||||||
|
UPDATE_ERROR_OK, /* UF2_ERR_IGNORE - block should be ignored */
|
||||||
|
UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_MAGIC - wrong magic numbers */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_FAMILY - family ID mismatched */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_NOT_HEADER - block is not a header */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_OTA_VER - unknown/invalid OTA format version */
|
||||||
|
UPDATE_ERROR_MAGIC_BYTE, /* UF2_ERR_OTA_WRONG - no data for current OTA index */
|
||||||
|
UPDATE_ERROR_NO_PARTITION, /* UF2_ERR_PART_404 - no partition with that name */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_ONE - only one partition tag in a block */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_PART_UNSET - attempted to write without target partition */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_DATA_TOO_LONG - data too long - tags won't fit */
|
||||||
|
UPDATE_ERROR_BAD_ARGUMENT, /* UF2_ERR_SEQ_MISMATCH - sequence number mismatched */
|
||||||
|
UPDATE_ERROR_ERASE, /* UF2_ERR_ERASE_FAILED - erasing flash failed */
|
||||||
|
UPDATE_ERROR_WRITE, /* UF2_ERR_WRITE_FAILED - writing to flash failed */
|
||||||
|
UPDATE_ERROR_WRITE /* UF2_ERR_WRITE_LENGTH - wrote fewer data than requested */
|
||||||
|
};
|
||||||
|
|
||||||
|
static char errorStr[14];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the callback invoked after writing data to flash.
|
||||||
|
*/
|
||||||
|
UpdateClass &UpdateClass::onProgress(THandlerFunction_Progress callback) {
|
||||||
|
this->callback = callback;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClass::cleanup() {
|
||||||
|
free(ctx); // NULL in constructor
|
||||||
|
ctx = NULL;
|
||||||
|
uf2_info_free(info); // NULL in constructor
|
||||||
|
info = NULL;
|
||||||
|
free(buf); // NULL in constructor
|
||||||
|
buf = bufPos = NULL;
|
||||||
|
|
||||||
|
bytesWritten = 0;
|
||||||
|
bytesTotal = 0;
|
||||||
|
errUf2 = UF2_ERR_OK;
|
||||||
|
errArd = UPDATE_ERROR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check for UF2 errors. Set errArd and errUf2 in case of errors.
|
||||||
|
* Ignored blocks are not reported as errors.
|
||||||
|
* Abort the update.
|
||||||
|
* Use like: "if (errorUf2(...)) return false;"
|
||||||
|
* @return true if err is not OK, false otherwise
|
||||||
|
*/
|
||||||
|
bool UpdateClass::errorUf2(uf2_err_t err) {
|
||||||
|
if (err <= UF2_ERR_IGNORE)
|
||||||
|
return false;
|
||||||
|
cleanup();
|
||||||
|
errUf2 = err;
|
||||||
|
errArd = errorMap[err];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set errUf2 and errArd according to given Arduino error code.
|
||||||
|
* Abort the update.
|
||||||
|
* Use like: "return errorArd(...);"
|
||||||
|
* @return false - always
|
||||||
|
*/
|
||||||
|
bool UpdateClass::errorArd(uint8_t err) {
|
||||||
|
cleanup();
|
||||||
|
errUf2 = UF2_ERR_OK;
|
||||||
|
errArd = err;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Abort the update with UPDATE_ERROR_ABORT reason.
|
||||||
|
*/
|
||||||
|
void UpdateClass::abort() {
|
||||||
|
errorArd(UPDATE_ERROR_ABORT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateClass::bufAlloc() {
|
||||||
|
if (!buf)
|
||||||
|
buf = bufPos = (uint8_t *)malloc(UF2_BLOCK_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t UpdateClass::bufLeft() {
|
||||||
|
return buf + UF2_BLOCK_SIZE - bufPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t UpdateClass::bufSize() {
|
||||||
|
return bufPos - buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print string error info to the stream.
|
||||||
|
*/
|
||||||
|
void UpdateClass::printError(Print &out) {
|
||||||
|
out.println(errorString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get string representation of the error in format
|
||||||
|
* "ard=..,uf2=..". Returns "" if no error.
|
||||||
|
*/
|
||||||
|
const char *UpdateClass::errorString() {
|
||||||
|
if (!errArd)
|
||||||
|
return "";
|
||||||
|
sprintf(errorStr, "ard=%u,uf2=%u", errArd, errUf2);
|
||||||
|
return errorStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get firmware name from UF2 info.
|
||||||
|
*/
|
||||||
|
const char *UpdateClass::getFirmwareName() {
|
||||||
|
if (info)
|
||||||
|
return info->fw_name;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get firmware version from UF2 info.
|
||||||
|
*/
|
||||||
|
const char *UpdateClass::getFirmwareVersion() {
|
||||||
|
if (info)
|
||||||
|
return info->fw_version;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get LibreTuya version from UF2 info.
|
||||||
|
*/
|
||||||
|
const char *UpdateClass::getLibreTuyaVersion() {
|
||||||
|
if (info)
|
||||||
|
return info->lt_version;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get target board name from UF2 info.
|
||||||
|
*/
|
||||||
|
const char *UpdateClass::getBoardName() {
|
||||||
|
if (info)
|
||||||
|
return info->board;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief See LT.otaCanRollback() for more info.
|
||||||
|
*/
|
||||||
|
bool UpdateClass::canRollBack() {
|
||||||
|
return LT.otaCanRollback();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief See LT.otaRollback() for more info.
|
||||||
|
*/
|
||||||
|
bool UpdateClass::rollBack() {
|
||||||
|
return LT.otaRollback();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user