Merge branch 'development' into rtsp_auth

This commit is contained in:
Nemobi 2022-05-20 10:42:45 +00:00
commit 02927be009
97 changed files with 5843 additions and 2672 deletions

View File

@ -38,10 +38,12 @@ jobs:
- tasmota32-ir
- tasmota32-lvgl
- tasmota32c3
- tasmota32c3usb
- tasmota32solo1
- tasmota32solo1-safeboot
- tasmota32-safeboot
- tasmota32c3-safeboot
- tasmota32c3usb-safeboot
- tasmota32s2-safeboot
- tasmota32s3-safeboot
steps:

View File

@ -37,12 +37,14 @@ jobs:
- tasmota32-ir
- tasmota32-lvgl
- tasmota32c3
- tasmota32c3usb
- tasmota32solo1
- tasmota32solo1-safeboot
- tasmota32-safeboot
- tasmota32c3-safeboot
- tasmota32s2-safeboot
- tasmota32s3-safeboot
- tasmota32c3usb-safeboot
steps:
- uses: actions/checkout@v3
with:

View File

@ -42,10 +42,12 @@ jobs:
- tasmota32-lvgl
- tasmota32s2
- tasmota32c3
- tasmota32c3usb
- tasmota32solo1
- tasmota32solo1-safeboot
- tasmota32-safeboot
- tasmota32c3-safeboot
- tasmota32c3usb-safeboot
- tasmota32s2-safeboot
- tasmota32s3-safeboot
steps:

View File

@ -3,13 +3,12 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - Development
## [11.1.0.2]
## [11.1.0.3]
### Added
- ESP32 Command ``Restart 3`` to switch between SafeBoot and Production
- Support for Sonoff SPM v1.2.0
### Changed
- Prepare to remove dedicated Home Assistant discovery in favour of Tasmota Discovery and hatasmota
- ESP32 Tasmota SafeBoot with changed partition scheme allowing larger binaries
### Fixed
@ -17,6 +16,14 @@ All notable changes to this project will be documented in this file.
### Removed
## [11.1.0.2] 20220514
### Added
- ESP32 Command ``Restart 3`` to switch between SafeBoot and Production
### Changed
- Prepare to remove dedicated Home Assistant discovery in favour of Tasmota Discovery and hatasmota
- ESP32 Tasmota SafeBoot with changed partition scheme allowing larger binaries
## [11.1.0.1] 20220504
### Added
- Support for Sonoff MS01 soil moisture sensor (#15335)

View File

@ -104,7 +104,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
[Complete list](BUILDS.md) of available feature and sensors.
## Changelog v11.1.0.2
## Changelog v11.1.0.3
### Added
- Command ``SetOption139 0/1`` to switch between pressure unit "mmHg" (0) or "inHg" (1) when ``SO24 1`` [#15350](https://github.com/arendst/Tasmota/issues/15350)
- Command ``SetOption140 0/1`` to switch between MQTT Clean Session (0) or Persistent Session (1) [#15530](https://github.com/arendst/Tasmota/issues/15530)
@ -113,6 +113,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- Support for Sonoff MS01 soil moisture sensor [#15335](https://github.com/arendst/Tasmota/issues/15335)
- Support for daisy chaining MAX7219 displays [#15345](https://github.com/arendst/Tasmota/issues/15345)
- Sonoff SPM delayed SetPowerOnState [#13447](https://github.com/arendst/Tasmota/issues/13447)
- Support for Sonoff SPM v1.2.0
- Support for flowrate meters like YF-DN50 and similary [#15474](https://github.com/arendst/Tasmota/issues/15474)
- ESP32 Command ``Restart 3`` to switch between SafeBoot and Production

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "esp32",
"partitions": "esp32_partition_app2880k_fs320k.csv"
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "m5stack_core2",
"partitions": "esp32_partition_app2944k_fs10M.csv"
"partitions": "partitions/esp32_partition_app2944k_fs10M.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "odroid_esp32",
"partitions": "esp32_partition_app2944k_fs10M.csv"
"partitions": "partitions/esp32_partition_app2944k_fs10M.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "esp32",
"partitions": "esp32_partition_app2944k_fs10M.csv"
"partitions": "partitions/esp32_partition_app2944k_fs10M.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "esp32",
"partitions": "esp32_partition_app2880k_fs320k.csv"
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "esp32",
"partitions": "esp32_partition_app1856k_fs1344k.csv"
"partitions": "partitions/esp32_partition_app1856k_fs1344k.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "esp32",
"partitions": "esp32_partition_app2944k_fs2M.csv"
"partitions": "partitions/esp32_partition_app2944k_fs2M.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32",
"variant": "esp32",
"partitions": "esp32_partition_app2880k_fs320k.csv"
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi",

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32c3",
"variant": "esp32c3",
"partitions": "esp32_partition_app2880k_fs320k.csv"
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi"

43
boards/esp32c3usb.json Normal file
View File

@ -0,0 +1,43 @@
{
"build": {
"arduino":{
"ldscript": "esp32c3_out.ld"
},
"core": "esp32",
"extra_flags": "-DARDUINO_USB_MODE=1 -DARDUINO_USB_CDC_ON_BOOT=0 -DESP32_4M -DESP32C3 -DUSE_USB_SERIAL_CONSOLE",
"f_cpu": "160000000L",
"f_flash": "80000000L",
"flash_mode": "dout",
"mcu": "esp32c3",
"variant": "esp32c3",
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi"
],
"debug": {
"openocd_target": "esp32c3.cfg"
},
"frameworks": [
"arduino",
"espidf"
],
"name": "Espressif Generic ESP32-C3 4M Flash, Tasmota 2880k Code/OTA, 320k FS",
"upload": {
"arduino": {
"flash_extra_images": [
[
"0x10000",
"variants/tasmota/tasmota32c3usb-safeboot.bin"
]
]
},
"flash_size": "4MB",
"maximum_ram_size": 327680,
"maximum_size": 4194304,
"require_upload_port": true,
"speed": 460800
},
"url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/hw-reference/esp32c3/user-guide-devkitm-1.html",
"vendor": "Espressif"
}

View File

@ -10,7 +10,7 @@
"flash_mode": "dout",
"mcu": "esp32s2",
"variant": "esp32s2",
"partitions": "esp32_partition_app2880k_fs320k.csv"
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi"

View File

@ -11,7 +11,7 @@
"flash_mode": "dio",
"mcu": "esp32s3",
"variant": "esp32s3",
"partitions": "esp32_partition_app2880k_fs320k.csv"
"partitions": "partitions/esp32_partition_app2880k_fs320k.csv"
},
"connectivity": [
"wifi",

View File

@ -11,7 +11,7 @@
"flash_mode": "dio",
"mcu": "esp32s3",
"variant": "esp32s3",
"partitions": "esp32_partition_app2944k_fs2M.csv"
"partitions": "partitions/esp32_partition_app2944k_fs2M.csv"
},
"connectivity": [
"wifi",

View File

@ -13,6 +13,13 @@ remove in file `lib/lib_audio/ESP8266Audio/src/AudioFileSourceFS.cpp`
#endif
```
add in file `lib/lib_audio/ESP8266Audio/src/spiram-fast.h`
```
#ifndef SPECIAL
#define SPECIAL 0xF0
#endif
```
Files to delete:
```
lib/lib_audio/ESP8266Audio/src/AudioFileSourceSPIFFS.h

View File

@ -117,7 +117,7 @@ String HTTPUpdateLight::getLastErrorString(void)
// error from Update class
if(_lastError > 0) {
StreamString error;
Update.printError(error);
TasUpdate.printError(error);
error.trim(); // remove line ending
return String("Update error: ") + error;
}
@ -418,14 +418,17 @@ bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int comma
StreamString error;
if (_cbProgress) {
Update.onProgress(_cbProgress);
TasUpdate.onProgress(_cbProgress);
}
if(!Update.begin(size, command, _ledPin, _ledOn)) {
_lastError = Update.getError();
Update.printError(error);
// Start Tasmota Factory patch
// if(!Update.begin(size, command, _ledPin, _ledOn)) {
if(!TasUpdate.begin(size, command, _ledPin, _ledOn, NULL, _factory)) {
// End Tasmota Factory patch
_lastError = TasUpdate.getError();
TasUpdate.printError(error);
error.trim(); // remove line ending
log_e("Update.begin failed! (%s)\n", error.c_str());
log_e("TasUpdate.begin failed! (%s)\n", error.c_str());
return false;
}
@ -434,20 +437,20 @@ bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int comma
}
if(md5.length()) {
if(!Update.setMD5(md5.c_str())) {
if(!TasUpdate.setMD5(md5.c_str())) {
_lastError = HTTP_UE_SERVER_FAULTY_MD5;
log_e("Update.setMD5 failed! (%s)\n", md5.c_str());
log_e("TasUpdate.setMD5 failed! (%s)\n", md5.c_str());
return false;
}
}
// To do: the SHA256 could be checked if the server sends it
if(Update.writeStream(in) != size) {
_lastError = Update.getError();
Update.printError(error);
if(TasUpdate.writeStream(in) != size) {
_lastError = TasUpdate.getError();
TasUpdate.printError(error);
error.trim(); // remove line ending
log_e("Update.writeStream failed! (%s)\n", error.c_str());
log_e("TasUpdate.writeStream failed! (%s)\n", error.c_str());
return false;
}
@ -455,11 +458,11 @@ bool HTTPUpdateLight::runUpdate(Stream& in, uint32_t size, String md5, int comma
_cbProgress(size, size);
}
if(!Update.end()) {
_lastError = Update.getError();
Update.printError(error);
if(!TasUpdate.end()) {
_lastError = TasUpdate.getError();
TasUpdate.printError(error);
error.trim(); // remove line ending
log_e("Update.end failed! (%s)\n", error.c_str());
log_e("TasUpdate.end failed! (%s)\n", error.c_str());
return false;
}

View File

@ -31,7 +31,7 @@
#include <WiFiClient.h>
#include <WiFiUdp.h>
#include <HttpClientLight.h>
#include <Update.h>
#include <TasUpdate.h>
#include <HTTPUpdate.h>
/// note we use HTTP client errors too so we start at 100
@ -69,7 +69,7 @@ public:
{
_rebootOnUpdate = reboot;
}
/**
* set redirect follow mode. See `followRedirects_t` enum for avaliable modes.
* @param follow
@ -85,6 +85,13 @@ public:
_ledOn = ledOn;
}
// Start Tasmota Factory patch
void setFactory(bool factory = false)
{
_factory = factory;
}
// End Tasmota Factory patch
// t_httpUpdate_return update(WiFiClient& client, const String& url, const String& currentVersion = "");
// t_httpUpdate_return update(WiFiClient& client, const String& host, uint16_t port, const String& uri = "/",
@ -131,6 +138,9 @@ private:
int _ledPin;
uint8_t _ledOn;
// Start Tasmota Factory patch
bool _factory;
// End Tasmota Factory patch
};
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_HTTPUPDATE)

View File

@ -0,0 +1,194 @@
#ifndef TASUPDATER_H
#define TASUPDATER_H
#include <Arduino.h>
#include <MD5Builder.h>
#include <functional>
#include "esp_partition.h"
#define UPDATE_ERROR_OK (0)
#define UPDATE_ERROR_WRITE (1)
#define UPDATE_ERROR_ERASE (2)
#define UPDATE_ERROR_READ (3)
#define UPDATE_ERROR_SPACE (4)
#define UPDATE_ERROR_SIZE (5)
#define UPDATE_ERROR_STREAM (6)
#define UPDATE_ERROR_MD5 (7)
#define UPDATE_ERROR_MAGIC_BYTE (8)
#define UPDATE_ERROR_ACTIVATE (9)
#define UPDATE_ERROR_NO_PARTITION (10)
#define UPDATE_ERROR_BAD_ARGUMENT (11)
#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
class TasUpdateClass {
public:
typedef std::function<void(size_t, size_t)> THandlerFunction_Progress;
TasUpdateClass();
/*
This callback will be called when Update is receiving data
*/
TasUpdateClass& onProgress(THandlerFunction_Progress fn);
/*
Call this to check the space needed for the update
Will return false if there is not enough space
*/
// Start Tasmota Factory patch
// bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL);
bool begin(size_t size=UPDATE_SIZE_UNKNOWN, int command = U_FLASH, int ledPin = -1, uint8_t ledOn = LOW, const char *label = NULL, bool factory = false);
// End Tasmota Factory patch
/*
Writes a buffer to the flash and increments the address
Returns the amount written
*/
size_t write(uint8_t *data, size_t len);
/*
Writes the remaining bytes from the Stream to the flash
Uses readBytes() and sets UPDATE_ERROR_STREAM on timeout
Returns the bytes written
Should be equal to the remaining bytes when called
Usable for slow streams like Serial
*/
size_t writeStream(Stream &data);
/*
If all bytes are written
this call will write the config to eboot
and return true
If there is already an update running but is not finished and !evenIfRemaining
or there is an error
this will clear everything and return false
the last error is available through getError()
evenIfRemaining is helpfull when you update without knowing the final size first
*/
bool end(bool evenIfRemaining = false);
/*
Aborts the running update
*/
void abort();
/*
Prints the last error to an output stream
*/
void printError(Print &out);
const char * errorString();
/*
sets the expected MD5 for the firmware (hexString)
*/
bool setMD5(const char * expected_md5);
/*
returns the MD5 String of the successfully ended firmware
*/
String md5String(void){ return _md5.toString(); }
/*
populated the result with the md5 bytes of the successfully ended firmware
*/
void md5(uint8_t * result){ return _md5.getBytes(result); }
//Helpers
uint8_t getError(){ return _error; }
void clearError(){ _error = UPDATE_ERROR_OK; }
bool hasError(){ return _error != UPDATE_ERROR_OK; }
bool isRunning(){ return _size > 0; }
bool isFinished(){ return _progress == _size; }
size_t size(){ return _size; }
size_t progress(){ return _progress; }
size_t remaining(){ return _size - _progress; }
/*
Template to write from objects that expose
available() and read(uint8_t*, size_t) methods
faster than the writeStream method
writes only what is available
*/
template<typename T>
size_t write(T &data){
size_t written = 0;
if (hasError() || !isRunning())
return 0;
size_t available = data.available();
while(available) {
if(_bufferLen + available > remaining()){
available = remaining() - _bufferLen;
}
if(_bufferLen + available > 4096) {
size_t toBuff = 4096 - _bufferLen;
data.read(_buffer + _bufferLen, toBuff);
_bufferLen += toBuff;
if(!_writeBuffer())
return written;
written += toBuff;
} else {
data.read(_buffer + _bufferLen, available);
_bufferLen += available;
written += available;
if(_bufferLen == remaining()) {
if(!_writeBuffer()) {
return written;
}
}
}
if(remaining() == 0)
return written;
available = data.available();
}
return written;
}
/*
check if there is a firmware on the other OTA partition that you can bootinto
*/
bool canRollBack();
/*
set the other OTA partition as bootable (reboot to enable)
*/
bool rollBack();
private:
void _reset();
void _abort(uint8_t err);
bool _writeBuffer();
bool _verifyHeader(uint8_t data);
bool _verifyEnd();
bool _enablePartition(const esp_partition_t* partition);
uint8_t _error;
uint8_t *_buffer;
uint8_t *_skipBuffer;
size_t _bufferLen;
size_t _size;
THandlerFunction_Progress _progress_callback;
uint32_t _progress;
uint32_t _paroffset;
uint32_t _command;
const esp_partition_t* _partition;
String _target_md5;
MD5Builder _md5;
int _ledPin;
uint8_t _ledOn;
};
extern TasUpdateClass TasUpdate;
#endif // TASUPDATER_H

View File

@ -0,0 +1,404 @@
#include "TasUpdate.h"
#include "Arduino.h"
#include "esp_spi_flash.h"
#include "esp_ota_ops.h"
#include "esp_image_format.h"
static const char * _err2str(uint8_t _error){
if(_error == UPDATE_ERROR_OK){
return ("No Error");
} else if(_error == UPDATE_ERROR_WRITE){
return ("Flash Write Failed");
} else if(_error == UPDATE_ERROR_ERASE){
return ("Flash Erase Failed");
} else if(_error == UPDATE_ERROR_READ){
return ("Flash Read Failed");
} else if(_error == UPDATE_ERROR_SPACE){
return ("Not Enough Space");
} else if(_error == UPDATE_ERROR_SIZE){
return ("Bad Size Given");
} else if(_error == UPDATE_ERROR_STREAM){
return ("Stream Read Timeout");
} else if(_error == UPDATE_ERROR_MD5){
return ("MD5 Check Failed");
} else if(_error == UPDATE_ERROR_MAGIC_BYTE){
return ("Wrong Magic Byte");
} else if(_error == UPDATE_ERROR_ACTIVATE){
return ("Could Not Activate The Firmware");
} else if(_error == UPDATE_ERROR_NO_PARTITION){
return ("Partition Could Not be Found");
} else if(_error == UPDATE_ERROR_BAD_ARGUMENT){
return ("Bad Argument");
} else if(_error == UPDATE_ERROR_ABORT){
return ("Aborted");
}
return ("UNKNOWN");
}
static bool _partitionIsBootable(const esp_partition_t* partition){
uint8_t buf[ENCRYPTED_BLOCK_SIZE];
if(!partition){
return false;
}
if(!ESP.partitionRead(partition, 0, (uint32_t*)buf, ENCRYPTED_BLOCK_SIZE)) {
return false;
}
if(buf[0] != ESP_IMAGE_HEADER_MAGIC) {
return false;
}
return true;
}
bool TasUpdateClass::_enablePartition(const esp_partition_t* partition){
if(!partition){
return false;
}
return ESP.partitionWrite(partition, 0, (uint32_t*) _skipBuffer, ENCRYPTED_BLOCK_SIZE);
}
TasUpdateClass::TasUpdateClass()
: _error(0)
, _buffer(0)
, _bufferLen(0)
, _size(0)
, _progress_callback(NULL)
, _progress(0)
, _paroffset(0)
, _command(U_FLASH)
, _partition(NULL)
{
}
TasUpdateClass& TasUpdateClass::onProgress(THandlerFunction_Progress fn) {
_progress_callback = fn;
return *this;
}
void TasUpdateClass::_reset() {
if (_buffer)
delete[] _buffer;
_buffer = 0;
_bufferLen = 0;
_progress = 0;
_size = 0;
_command = U_FLASH;
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // off
}
}
bool TasUpdateClass::canRollBack(){
if(_buffer){ //Update is running
return false;
}
const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL);
return _partitionIsBootable(partition);
}
bool TasUpdateClass::rollBack(){
if(_buffer){ //Update is running
return false;
}
const esp_partition_t* partition = esp_ota_get_next_update_partition(NULL);
return _partitionIsBootable(partition) && !esp_ota_set_boot_partition(partition);
}
// Start Tasmota Factory patch
//bool UpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label) {
bool TasUpdateClass::begin(size_t size, int command, int ledPin, uint8_t ledOn, const char *label, bool factory) {
// End Tasmota Factory patch
if(_size > 0){
log_w("already running");
return false;
}
_ledPin = ledPin;
_ledOn = !!ledOn; // 0(LOW) or 1(HIGH)
_reset();
_error = 0;
_target_md5 = emptyString;
_md5 = MD5Builder();
if(size == 0) {
_error = UPDATE_ERROR_SIZE;
return false;
}
if (command == U_FLASH) {
// Start Tasmota Factory patch
// _partition = esp_ota_get_next_update_partition(NULL);
if (factory) {
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, NULL);
} else {
_partition = esp_ota_get_next_update_partition(NULL);
}
// End Tasmota Factory patch
if(!_partition){
_error = UPDATE_ERROR_NO_PARTITION;
return false;
}
log_d("OTA Partition: %s", _partition->label);
}
else if (command == U_SPIFFS) {
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_SPIFFS, label);
_paroffset = 0;
if(!_partition){
_partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_FAT, NULL);
_paroffset = 0x1000; //Offset for ffat, assuming size is already corrected
if(!_partition){
_error = UPDATE_ERROR_NO_PARTITION;
return false;
}
}
}
else {
_error = UPDATE_ERROR_BAD_ARGUMENT;
log_e("bad command %u", command);
return false;
}
if(size == UPDATE_SIZE_UNKNOWN){
size = _partition->size;
} else if(size > _partition->size){
_error = UPDATE_ERROR_SIZE;
log_e("too large %u > %u", size, _partition->size);
return false;
}
//initialize
_buffer = (uint8_t*)malloc(SPI_FLASH_SEC_SIZE);
if(!_buffer){
log_e("malloc failed");
return false;
}
_size = size;
_command = command;
_md5.begin();
return true;
}
void TasUpdateClass::_abort(uint8_t err){
_reset();
_error = err;
}
void TasUpdateClass::abort(){
_abort(UPDATE_ERROR_ABORT);
}
bool TasUpdateClass::_writeBuffer(){
//first bytes of new firmware
uint8_t skip = 0;
if(!_progress && _command == U_FLASH){
//check magic
if(_buffer[0] != ESP_IMAGE_HEADER_MAGIC){
_abort(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
//Stash the first 16 bytes of data and set the offset so they are
//not written at this point so that partially written firmware
//will not be bootable
skip = ENCRYPTED_BLOCK_SIZE;
_skipBuffer = (uint8_t*)malloc(skip);
if(!_skipBuffer){
log_e("malloc failed");
return false;
}
memcpy(_skipBuffer, _buffer, skip);
}
if (!_progress && _progress_callback) {
_progress_callback(0, _size);
}
if(!ESP.partitionEraseRange(_partition, _progress, SPI_FLASH_SEC_SIZE)){
_abort(UPDATE_ERROR_ERASE);
return false;
}
if (!ESP.partitionWrite(_partition, _progress + skip, (uint32_t*)_buffer + skip/sizeof(uint32_t), _bufferLen - skip)) {
_abort(UPDATE_ERROR_WRITE);
return false;
}
//restore magic or md5 will fail
if(!_progress && _command == U_FLASH){
_buffer[0] = ESP_IMAGE_HEADER_MAGIC;
}
_md5.add(_buffer, _bufferLen);
_progress += _bufferLen;
_bufferLen = 0;
if (_progress_callback) {
_progress_callback(_progress, _size);
}
return true;
}
bool TasUpdateClass::_verifyHeader(uint8_t data) {
if(_command == U_FLASH) {
if(data != ESP_IMAGE_HEADER_MAGIC) {
_abort(UPDATE_ERROR_MAGIC_BYTE);
return false;
}
return true;
} else if(_command == U_SPIFFS) {
return true;
}
return false;
}
bool TasUpdateClass::_verifyEnd() {
if(_command == U_FLASH) {
if(!_enablePartition(_partition) || !_partitionIsBootable(_partition)) {
_abort(UPDATE_ERROR_READ);
return false;
}
if(esp_ota_set_boot_partition(_partition)){
_abort(UPDATE_ERROR_ACTIVATE);
return false;
}
_reset();
return true;
} else if(_command == U_SPIFFS) {
_reset();
return true;
}
return false;
}
bool TasUpdateClass::setMD5(const char * expected_md5){
if(strlen(expected_md5) != 32)
{
return false;
}
_target_md5 = expected_md5;
return true;
}
bool TasUpdateClass::end(bool evenIfRemaining){
if(hasError() || _size == 0){
return false;
}
if(!isFinished() && !evenIfRemaining){
log_e("premature end: res:%u, pos:%u/%u\n", getError(), progress(), _size);
_abort(UPDATE_ERROR_ABORT);
return false;
}
if(evenIfRemaining) {
if(_bufferLen > 0) {
_writeBuffer();
}
_size = progress();
}
_md5.calculate();
if(_target_md5.length()) {
if(_target_md5 != _md5.toString()){
_abort(UPDATE_ERROR_MD5);
return false;
}
}
return _verifyEnd();
}
size_t TasUpdateClass::write(uint8_t *data, size_t len) {
if(hasError() || !isRunning()){
return 0;
}
if(len > remaining()){
_abort(UPDATE_ERROR_SPACE);
return 0;
}
size_t left = len;
while((_bufferLen + left) > SPI_FLASH_SEC_SIZE) {
size_t toBuff = SPI_FLASH_SEC_SIZE - _bufferLen;
memcpy(_buffer + _bufferLen, data + (len - left), toBuff);
_bufferLen += toBuff;
if(!_writeBuffer()){
return len - left;
}
left -= toBuff;
}
memcpy(_buffer + _bufferLen, data + (len - left), left);
_bufferLen += left;
if(_bufferLen == remaining()){
if(!_writeBuffer()){
return len - left;
}
}
return len;
}
size_t TasUpdateClass::writeStream(Stream &data) {
size_t written = 0;
size_t toRead = 0;
int timeout_failures = 0;
if(hasError() || !isRunning())
return 0;
if(!_verifyHeader(data.peek())) {
_reset();
return 0;
}
if(_ledPin != -1) {
pinMode(_ledPin, OUTPUT);
}
while(remaining()) {
if(_ledPin != -1) {
digitalWrite(_ledPin, _ledOn); // Switch LED on
}
size_t bytesToRead = SPI_FLASH_SEC_SIZE - _bufferLen;
if(bytesToRead > remaining()) {
bytesToRead = remaining();
}
/*
Init read&timeout counters and try to read, if read failed, increase counter,
wait 100ms and try to read again. If counter > 300 (30 sec), give up/abort
*/
toRead = 0;
timeout_failures = 0;
while(!toRead) {
toRead = data.readBytes(_buffer + _bufferLen, bytesToRead);
if(toRead == 0) {
timeout_failures++;
if (timeout_failures >= 300) {
_abort(UPDATE_ERROR_STREAM);
return written;
}
delay(100);
}
}
if(_ledPin != -1) {
digitalWrite(_ledPin, !_ledOn); // Switch LED off
}
_bufferLen += toRead;
if((_bufferLen == remaining() || _bufferLen == SPI_FLASH_SEC_SIZE) && !_writeBuffer())
return written;
written += toRead;
delay(1); // Fix solo WDT
}
return written;
}
void TasUpdateClass::printError(Print &out){
out.println(_err2str(_error));
}
const char * TasUpdateClass::errorString(){
return _err2str(_error);
}
TasUpdateClass TasUpdate;

View File

@ -45,7 +45,6 @@ extern void analogWritePhase(uint8_t pin, uint32_t duty, uint32_t phase = 0);
#define ETS_UART_INTR_ENABLE()
#define ESPhttpUpdate httpUpdate
#define getFlashChipRealSize() getFlashChipSize()
#define os_delay_us ets_delay_us
// Serial minimal type to hold the config

View File

@ -788,7 +788,6 @@ extern const bcstring be_const_str_return;
extern const bcstring be_const_str_return_X20code_X3D_X25i;
extern const bcstring be_const_str_reverse;
extern const bcstring be_const_str_reverse_gamma10;
extern const bcstring be_const_str_rollback;
extern const bcstring be_const_str_rotate;
extern const bcstring be_const_str_round_end;
extern const bcstring be_const_str_round_start;

View File

@ -607,7 +607,7 @@ be_define_const_str(length_X20in_X20bits_X20must_X20be_X20between_X200_X20and_X2
be_define_const_str(light, "light", 3801947695u, 0, 5, &be_const_str_set_ldo_voltage);
be_define_const_str(light_X20must_X20be_X20of_X20class_X20_X27light_state_X27, "light must be of class 'light_state'", 3669350396u, 0, 36, &be_const_str_read_sensors);
be_define_const_str(light_state, "light_state", 905783845u, 0, 11, &be_const_str_lv_wifi_bars_icon);
be_define_const_str(light_to_id, "light_to_id", 1117015647u, 0, 11, &be_const_str_rollback);
be_define_const_str(light_to_id, "light_to_id", 1117015647u, 0, 11, NULL);
be_define_const_str(lights, "lights", 425118420u, 0, 6, &be_const_str_set_size);
be_define_const_str(line_dsc, "line_dsc", 4094490978u, 0, 8, &be_const_str_try_run_compiled);
be_define_const_str(list, "list", 217798785u, 0, 4, &be_const_str_set_MAC);
@ -780,7 +780,6 @@ be_define_const_str(return, "return", 2246981567u, 60, 6, NULL);
be_define_const_str(return_X20code_X3D_X25i, "return code=%i", 2127454401u, 0, 14, &be_const_str_write_bit);
be_define_const_str(reverse, "reverse", 558918661u, 0, 7, &be_const_str_sin);
be_define_const_str(reverse_gamma10, "reverse_gamma10", 739112262u, 0, 15, NULL);
be_define_const_str(rollback, "rollback", 2093668477u, 0, 8, NULL);
be_define_const_str(rotate, "rotate", 2784296202u, 0, 6, &be_const_str_set_active);
be_define_const_str(round_end, "round_end", 985288225u, 0, 9, NULL);
be_define_const_str(round_start, "round_start", 2949484384u, 0, 11, &be_const_str_tolower);
@ -1543,6 +1542,6 @@ static const bstring* const m_string_table[] = {
static const struct bconststrtab m_const_string_table = {
.size = 505,
.count = 1034,
.count = 1033,
.table = m_string_table
};

View File

@ -222,6 +222,7 @@ intptr_t be_convert_single_elt(bvm *vm, int idx, const char * arg_type, int *buf
type_ok = (arg_type[0] == '.'); // any type is accepted
type_ok = type_ok || (arg_type[0] == provided_type && arg_type[1] == 0); // or type is a match (single char only)
type_ok = type_ok || (ret == 0 && arg_type_len != 1); // or NULL is accepted for an instance
type_ok = type_ok || (ret == 0 && arg_type[0] == 's' && arg_type[1] == 0); // accept nil for string, can be dangerous
if (!type_ok) {
be_raisef(vm, "type_error", "Unexpected argument type '%c', expected '%s'", provided_type, arg_type);

File diff suppressed because it is too large Load Diff

View File

@ -696,8 +696,8 @@ be_local_closure(Partition_get_active, /* name */
********************************************************************/
be_local_closure(Partition_switch_factory, /* name */
be_nested_proto(
4, /* nstack */
1, /* argc */
6, /* nstack */
2, /* argc */
2, /* varg */
0, /* has upvals */
NULL, /* no upvals */
@ -706,15 +706,16 @@ be_local_closure(Partition_switch_factory, /* name */
1, /* has constants */
( &(const bvalue[ 2]) { /* constants */
/* K0 */ be_nested_str(flash),
/* K1 */ be_nested_str(rollback),
/* K1 */ be_nested_str(factory),
}),
&be_const_str_switch_factory,
&be_const_str_solidified,
( &(const binstruction[ 4]) { /* code */
0xA4060000, // 0000 IMPORT R1 K0
0x8C080301, // 0001 GETMET R2 R1 K1
0x7C080200, // 0002 CALL R2 1
0x80000000, // 0003 RET 0
( &(const binstruction[ 5]) { /* code */
0xA40A0000, // 0000 IMPORT R2 K0
0x8C0C0501, // 0001 GETMET R3 R2 K1
0x5C140200, // 0002 MOVE R5 R1
0x7C0C0400, // 0003 CALL R3 2
0x80000000, // 0004 RET 0
})
)
);
@ -837,44 +838,47 @@ be_local_closure(Partition_ota_max, /* name */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[ 5]) { /* constants */
/* K0 */ be_const_int(0),
/* K1 */ be_nested_str(slots),
/* K2 */ be_nested_str(type),
/* K0 */ be_nested_str(slots),
/* K1 */ be_nested_str(type),
/* K2 */ be_const_int(0),
/* K3 */ be_nested_str(subtype),
/* K4 */ be_nested_str(stop_iteration),
}),
&be_const_str_ota_max,
&be_const_str_solidified,
( &(const binstruction[29]) { /* code */
0x58040000, // 0000 LDCONST R1 K0
( &(const binstruction[32]) { /* code */
0x4C040000, // 0000 LDNIL R1
0x60080010, // 0001 GETGBL R2 G16
0x880C0101, // 0002 GETMBR R3 R0 K1
0x880C0100, // 0002 GETMBR R3 R0 K0
0x7C080200, // 0003 CALL R2 1
0xA8020013, // 0004 EXBLK 0 #0019
0xA8020016, // 0004 EXBLK 0 #001C
0x5C0C0400, // 0005 MOVE R3 R2
0x7C0C0000, // 0006 CALL R3 0
0x88100702, // 0007 GETMBR R4 R3 K2
0x1C100900, // 0008 EQ R4 R4 K0
0x7812000D, // 0009 JMPF R4 #0018
0x88100701, // 0007 GETMBR R4 R3 K1
0x1C100902, // 0008 EQ R4 R4 K2
0x78120010, // 0009 JMPF R4 #001B
0x88100703, // 000A GETMBR R4 R3 K3
0x5416000F, // 000B LDINT R5 16
0x28100805, // 000C GE R4 R4 R5
0x78120009, // 000D JMPF R4 #0018
0x7812000C, // 000D JMPF R4 #001B
0x88100703, // 000E GETMBR R4 R3 K3
0x5416001F, // 000F LDINT R5 32
0x14100805, // 0010 LT R4 R4 R5
0x78120005, // 0011 JMPF R4 #0018
0x78120008, // 0011 JMPF R4 #001B
0x88100703, // 0012 GETMBR R4 R3 K3
0x5416000F, // 0013 LDINT R5 16
0x04100805, // 0014 SUB R4 R4 R5
0x24140801, // 0015 GT R5 R4 R1
0x78160000, // 0016 JMPF R5 #0018
0x5C040800, // 0017 MOVE R1 R4
0x7001FFEB, // 0018 JMP #0005
0x58080004, // 0019 LDCONST R2 K4
0xAC080200, // 001A CATCH R2 1 0
0xB0080000, // 001B RAISE 2 R0 R0
0x80040200, // 001C RET 1 R1
0x4C140000, // 0015 LDNIL R5
0x1C140205, // 0016 EQ R5 R1 R5
0x74160001, // 0017 JMPT R5 #001A
0x24140801, // 0018 GT R5 R4 R1
0x78160000, // 0019 JMPF R5 #001B
0x5C040800, // 001A MOVE R1 R4
0x7001FFE8, // 001B JMP #0005
0x58080004, // 001C LDCONST R2 K4
0xAC080200, // 001D CATCH R2 1 0
0xB0080000, // 001E RAISE 2 R0 R0
0x80040200, // 001F RET 1 R1
})
)
);
@ -1842,7 +1846,7 @@ be_local_closure(Partition_info_is_ota, /* name */
********************************************************************/
be_local_closure(Partition_info_get_image_size, /* name */
be_nested_proto(
13, /* nstack */
14, /* nstack */
1, /* argc */
2, /* varg */
0, /* has upvals */
@ -1850,24 +1854,25 @@ be_local_closure(Partition_info_get_image_size, /* name */
0, /* has sup protos */
NULL, /* no sub protos */
1, /* has constants */
( &(const bvalue[13]) { /* constants */
( &(const bvalue[14]) { /* constants */
/* K0 */ be_nested_str(flash),
/* K1 */ be_nested_str(is_ota),
/* K2 */ be_nested_str(is_factory),
/* K3 */ be_nested_str(start),
/* K4 */ be_nested_str(read),
/* K5 */ be_const_int(1),
/* K6 */ be_nested_str(get),
/* K7 */ be_const_int(0),
/* K8 */ be_nested_str(tasmota),
/* K9 */ be_nested_str(log),
/* K10 */ be_nested_str(BRY_X3A_X20Exception_X3E_X20_X27),
/* K11 */ be_nested_str(_X27_X20_X2D_X20),
/* K12 */ be_const_int(2),
/* K4 */ be_nested_str(size),
/* K5 */ be_nested_str(read),
/* K6 */ be_const_int(1),
/* K7 */ be_nested_str(get),
/* K8 */ be_const_int(0),
/* K9 */ be_nested_str(tasmota),
/* K10 */ be_nested_str(log),
/* K11 */ be_nested_str(BRY_X3A_X20Exception_X3E_X20_X27),
/* K12 */ be_nested_str(_X27_X20_X2D_X20),
/* K13 */ be_const_int(2),
}),
&be_const_str_get_image_size,
&be_const_str_solidified,
( &(const binstruction[78]) { /* code */
( &(const binstruction[85]) { /* code */
0xA4060000, // 0000 IMPORT R1 K0
0x8C080101, // 0001 GETMET R2 R0 K1
0x7C080200, // 0002 CALL R2 1
@ -1879,73 +1884,80 @@ be_local_closure(Partition_info_get_image_size, /* name */
0x740A0001, // 0008 JMPT R2 #000B
0x5409FFFE, // 0009 LDINT R2 -1
0x80040400, // 000A RET 1 R2
0xA8020033, // 000B EXBLK 0 #0040
0xA802003A, // 000B EXBLK 0 #0047
0x88080103, // 000C GETMBR R2 R0 K3
0x8C0C0304, // 000D GETMET R3 R1 K4
0x5C140400, // 000E MOVE R5 R2
0x58180005, // 000F LDCONST R6 K5
0x7C0C0600, // 0010 CALL R3 3
0x8C0C0706, // 0011 GETMET R3 R3 K6
0x58140007, // 0012 LDCONST R5 K7
0x58180005, // 0013 LDCONST R6 K5
0x7C0C0600, // 0014 CALL R3 3
0x541200E8, // 0015 LDINT R4 233
0x20100604, // 0016 NE R4 R3 R4
0x78120002, // 0017 JMPF R4 #001B
0x5411FFFE, // 0018 LDINT R4 -1
0xA8040001, // 0019 EXBLK 1 1
0x80040800, // 001A RET 1 R4
0x8C100304, // 001B GETMET R4 R1 K4
0x00180505, // 001C ADD R6 R2 K5
0x581C0005, // 001D LDCONST R7 K5
0x7C100600, // 001E CALL R4 3
0x8C100906, // 001F GETMET R4 R4 K6
0x58180007, // 0020 LDCONST R6 K7
0x581C0005, // 0021 LDCONST R7 K5
0x7C100600, // 0022 CALL R4 3
0x5416001F, // 0023 LDINT R5 32
0x00140405, // 0024 ADD R5 R2 R5
0x58180007, // 0025 LDCONST R6 K7
0x141C0C04, // 0026 LT R7 R6 R4
0x781E0011, // 0027 JMPF R7 #003A
0x8C1C0304, // 0028 GETMET R7 R1 K4
0x54260007, // 0029 LDINT R9 8
0x04240A09, // 002A SUB R9 R5 R9
0x542A0007, // 002B LDINT R10 8
0x7C1C0600, // 002C CALL R7 3
0x8C200F06, // 002D GETMET R8 R7 K6
0x58280007, // 002E LDCONST R10 K7
0x542E0003, // 002F LDINT R11 4
0x7C200600, // 0030 CALL R8 3
0x8C240F06, // 0031 GETMET R9 R7 K6
0x542E0003, // 0032 LDINT R11 4
0x880C0104, // 000D GETMBR R3 R0 K4
0x8C100305, // 000E GETMET R4 R1 K5
0x5C180400, // 000F MOVE R6 R2
0x581C0006, // 0010 LDCONST R7 K6
0x7C100600, // 0011 CALL R4 3
0x8C100907, // 0012 GETMET R4 R4 K7
0x58180008, // 0013 LDCONST R6 K8
0x581C0006, // 0014 LDCONST R7 K6
0x7C100600, // 0015 CALL R4 3
0x541600E8, // 0016 LDINT R5 233
0x20140805, // 0017 NE R5 R4 R5
0x78160002, // 0018 JMPF R5 #001C
0x5415FFFE, // 0019 LDINT R5 -1
0xA8040001, // 001A EXBLK 1 1
0x80040A00, // 001B RET 1 R5
0x8C140305, // 001C GETMET R5 R1 K5
0x001C0506, // 001D ADD R7 R2 K6
0x58200006, // 001E LDCONST R8 K6
0x7C140600, // 001F CALL R5 3
0x8C140B07, // 0020 GETMET R5 R5 K7
0x581C0008, // 0021 LDCONST R7 K8
0x58200006, // 0022 LDCONST R8 K6
0x7C140600, // 0023 CALL R5 3
0x541A001F, // 0024 LDINT R6 32
0x00180406, // 0025 ADD R6 R2 R6
0x581C0008, // 0026 LDCONST R7 K8
0x14200E05, // 0027 LT R8 R7 R5
0x78220017, // 0028 JMPF R8 #0041
0x8C200305, // 0029 GETMET R8 R1 K5
0x542A0007, // 002A LDINT R10 8
0x04280C0A, // 002B SUB R10 R6 R10
0x542E0007, // 002C LDINT R11 8
0x7C200600, // 002D CALL R8 3
0x8C241107, // 002E GETMET R9 R8 K7
0x582C0008, // 002F LDCONST R11 K8
0x54320003, // 0030 LDINT R12 4
0x7C240600, // 0031 CALL R9 3
0x8C281107, // 0032 GETMET R10 R8 K7
0x54320003, // 0033 LDINT R12 4
0x7C240600, // 0034 CALL R9 3
0x542A0007, // 0035 LDINT R10 8
0x0028120A, // 0036 ADD R10 R9 R10
0x00140A0A, // 0037 ADD R5 R5 R10
0x00180D05, // 0038 ADD R6 R6 K5
0x7001FFEB, // 0039 JMP #0026
0x041C0A02, // 003A SUB R7 R5 R2
0x001C0F05, // 003B ADD R7 R7 K5
0xA8040001, // 003C EXBLK 1 1
0x80040E00, // 003D RET 1 R7
0xA8040001, // 003E EXBLK 1 1
0x7002000C, // 003F JMP #004D
0xAC080002, // 0040 CATCH R2 0 2
0x70020009, // 0041 JMP #004C
0xB8121000, // 0042 GETNGBL R4 K8
0x8C100909, // 0043 GETMET R4 R4 K9
0x001A1402, // 0044 ADD R6 K10 R2
0x00180D0B, // 0045 ADD R6 R6 K11
0x00180C03, // 0046 ADD R6 R6 R3
0x581C000C, // 0047 LDCONST R7 K12
0x7C100600, // 0048 CALL R4 3
0x5411FFFE, // 0049 LDINT R4 -1
0x80040800, // 004A RET 1 R4
0x70020000, // 004B JMP #004D
0xB0080000, // 004C RAISE 2 R0 R0
0x80000000, // 004D RET 0
0x54360003, // 0034 LDINT R13 4
0x7C280600, // 0035 CALL R10 3
0x542E0007, // 0036 LDINT R11 8
0x002C140B, // 0037 ADD R11 R10 R11
0x00180C0B, // 0038 ADD R6 R6 R11
0x002C0403, // 0039 ADD R11 R2 R3
0x282C0C0B, // 003A GE R11 R6 R11
0x782E0002, // 003B JMPF R11 #003F
0x542DFFFE, // 003C LDINT R11 -1
0xA8040001, // 003D EXBLK 1 1
0x80041600, // 003E RET 1 R11
0x001C0F06, // 003F ADD R7 R7 K6
0x7001FFE5, // 0040 JMP #0027
0x04200C02, // 0041 SUB R8 R6 R2
0x00201106, // 0042 ADD R8 R8 K6
0xA8040001, // 0043 EXBLK 1 1
0x80041000, // 0044 RET 1 R8
0xA8040001, // 0045 EXBLK 1 1
0x7002000C, // 0046 JMP #0054
0xAC080002, // 0047 CATCH R2 0 2
0x70020009, // 0048 JMP #0053
0xB8121200, // 0049 GETNGBL R4 K9
0x8C10090A, // 004A GETMET R4 R4 K10
0x001A1602, // 004B ADD R6 K11 R2
0x00180D0C, // 004C ADD R6 R6 K12
0x00180C03, // 004D ADD R6 R6 R3
0x581C000D, // 004E LDCONST R7 K13
0x7C100600, // 004F CALL R4 3
0x5411FFFE, // 0050 LDINT R4 -1
0x80040800, // 0051 RET 1 R4
0x70020000, // 0052 JMP #0054
0xB0080000, // 0053 RAISE 2 R0 R0
0x80000000, // 0054 RET 0
})
)
);

View File

@ -109,6 +109,7 @@ class Partition_info
if self.is_ota() == nil && !self.is_factory() return -1 end
try
var addr = self.start
var size = self.size
var magic_byte = flash.read(addr, 1).get(0, 1)
if magic_byte != 0xE9 return -1 end
@ -126,6 +127,7 @@ class Partition_info
# print(string.format("Segment %i: flash_offset=0x%08X start_addr=0x%08X size=0x%08X", seg_num, seg_offset, seg_start_addr, seg_size))
seg_offset += seg_size + 8 # add segment_length + sizeof(esp_image_segment_header_t)
if seg_offset >= (addr + size) return -1 end
seg_num += 1
end
@ -420,11 +422,11 @@ class Partition
#- compute the highest ota<x> partition -#
def ota_max()
var ota_max = 0
var ota_max = nil
for slot:self.slots
if slot.type == 0 && (slot.subtype >= 0x10 && slot.subtype < 0x20)
var ota_num = slot.subtype - 0x10
if ota_num > ota_max ota_max = ota_num end
if (ota_max == nil) || (ota_num > ota_max) ota_max = ota_num end
end
end
return ota_max
@ -513,9 +515,9 @@ class Partition
end
# switch to safeboot `factory` partition
def switch_factory()
def switch_factory(force_ota)
import flash
flash.rollback()
flash.factory(force_ota)
end
end
partition_core.Partition = Partition

View File

@ -875,7 +875,7 @@ const be_ntv_func_def_t lv_dropdown_func[] = {
{ "get_options", { (const void*) &lv_dropdown_get_options, "s", "(lv.lv_obj)" } },
{ "get_selected", { (const void*) &lv_dropdown_get_selected, "i", "(lv.lv_obj)" } },
{ "get_selected_highlight", { (const void*) &lv_dropdown_get_selected_highlight, "b", "(lv.lv_obj)" } },
{ "get_selected_str", { (const void*) &lv_dropdown_get_selected_str, "", "(lv.lv_obj)si" } },
{ "get_selected_str", { (const void*) &lv_dropdown_get_selected_str, "", "(lv.lv_obj)ci" } },
{ "get_symbol", { (const void*) &lv_dropdown_get_symbol, "s", "(lv.lv_obj)" } },
{ "get_text", { (const void*) &lv_dropdown_get_text, "s", "(lv.lv_obj)" } },
{ "is_open", { (const void*) &lv_dropdown_is_open, "b", "(lv.lv_obj)" } },
@ -898,7 +898,7 @@ const be_ntv_func_def_t lv_label_func[] = {
{ "get_letter_pos", { (const void*) &lv_label_get_letter_pos, "", "(lv.lv_obj)i(lv.lv_point)" } },
{ "get_long_mode", { (const void*) &lv_label_get_long_mode, "i", "(lv.lv_obj)" } },
{ "get_recolor", { (const void*) &lv_label_get_recolor, "b", "(lv.lv_obj)" } },
{ "get_text", { (const void*) &lv_label_get_text, "s", "(lv.lv_obj)" } },
{ "get_text", { (const void*) &lv_label_get_text, "c", "(lv.lv_obj)" } },
{ "get_text_selection_end", { (const void*) &lv_label_get_text_selection_end, "i", "(lv.lv_obj)" } },
{ "get_text_selection_start", { (const void*) &lv_label_get_text_selection_start, "i", "(lv.lv_obj)" } },
{ "ins_text", { (const void*) &lv_label_ins_text, "", "(lv.lv_obj)is" } },
@ -928,7 +928,7 @@ const be_ntv_func_def_t lv_roller_func[] = {
{ "get_option_cnt", { (const void*) &lv_roller_get_option_cnt, "i", "(lv.lv_obj)" } },
{ "get_options", { (const void*) &lv_roller_get_options, "s", "(lv.lv_obj)" } },
{ "get_selected", { (const void*) &lv_roller_get_selected, "i", "(lv.lv_obj)" } },
{ "get_selected_str", { (const void*) &lv_roller_get_selected_str, "", "(lv.lv_obj)si" } },
{ "get_selected_str", { (const void*) &lv_roller_get_selected_str, "", "(lv.lv_obj)ci" } },
{ "set_options", { (const void*) &lv_roller_set_options, "", "(lv.lv_obj)s(lv.lv_roller_mode)" } },
{ "set_selected", { (const void*) &lv_roller_set_selected, "", "(lv.lv_obj)ii" } },
{ "set_visible_row_count", { (const void*) &lv_roller_set_visible_row_count, "", "(lv.lv_obj)i" } },

View File

@ -75,7 +75,7 @@ const be_ntv_func_def_t lv_func[] = {
{ "draw_arc_dsc_init", { (const void*) &lv_draw_arc_dsc_init, "", "(lv.lv_draw_arc_dsc)" } },
{ "draw_arc_get_area", { (const void*) &lv_draw_arc_get_area, "", "iiiiiib(lv.lv_area)" } },
{ "draw_img", { (const void*) &lv_draw_img, "", "(lv.lv_draw_ctx)(lv.lv_draw_img_dsc)(lv.lv_area)." } },
{ "draw_img_decoded", { (const void*) &lv_draw_img_decoded, "", "(lv.lv_draw_ctx)(lv.lv_draw_img_dsc)(lv.lv_area)(lv.uint8)i" } },
{ "draw_img_decoded", { (const void*) &lv_draw_img_decoded, "", "(lv.lv_draw_ctx)(lv.lv_draw_img_dsc)(lv.lv_area)ci" } },
{ "draw_img_dsc_init", { (const void*) &lv_draw_img_dsc_init, "", "(lv.lv_draw_img_dsc)" } },
{ "draw_init", { (const void*) &lv_draw_init, "", "" } },
{ "draw_label", { (const void*) &lv_draw_label, "", "(lv.lv_draw_ctx)(lv.lv_draw_label_dsc)(lv.lv_area)s(lv.lv_draw_label_hint)" } },
@ -109,6 +109,7 @@ const be_ntv_func_def_t lv_func[] = {
{ "event_stop_bubbling", { (const void*) &lv_event_stop_bubbling, "", "(lv.lv_event)" } },
{ "event_stop_processing", { (const void*) &lv_event_stop_processing, "", "(lv.lv_event)" } },
{ "flex_init", { (const void*) &lv_flex_init, "", "" } },
{ "font_get_glyph_bitmap", { (const void*) &lv_font_get_glyph_bitmap, "c", "(lv.lv_font)i" } },
{ "font_get_glyph_dsc", { (const void*) &lv_font_get_glyph_dsc, "b", "(lv.lv_font)(lv.lv_font_glyph_dsc)ii" } },
{ "font_get_glyph_width", { (const void*) &lv_font_get_glyph_width, "i", "(lv.lv_font)ii" } },
{ "font_get_line_height", { (const void*) &lv_font_get_line_height, "i", "(lv.lv_font)" } },

View File

@ -35,8 +35,10 @@ return_types = {
"int32_t": "i",
"void *": ".",
"const void *": ".",
"char *": "s",
"char *": "c",
"uint8_t *": "c",
"const char *": "s",
"constchar *": "s", # special construct
"lv_obj_user_data_t": "i",
"lv_objmask_mask_t *": ".",
@ -122,6 +124,7 @@ return_types = {
"lv_anim_start_cb_t": "c",
# arrays
"constchar * []": "str_arr",
"char * []": "str_arr",
"lv_coord_t []": "lv_coord_arr",
"lv_point_t []": "lv_point_arr",
@ -161,6 +164,7 @@ return_types = {
"lv_timer_t *": "lv_timer",
"lv_coord_t *": "c", # treat as a simple pointer, decoding needs to be done at Berry level
"char **": "c", # treat as a simple pointer, decoding needs to be done at Berry level
"constchar **": "c", # treat as a simple pointer, decoding needs to be done at Berry level
# callbacks
"lv_group_focus_cb_t": "lv_group_focus_cb",
@ -229,6 +233,7 @@ with open(lv_widgets_file) as f:
l_raw = l_raw.strip(" \t\n\r") # remove leading or trailing spaces
l_raw = re.sub('static ', '', l_raw)
l_raw = re.sub('inline ', '', l_raw)
l_raw = re.sub('const\s+char\s*\*', 'constchar *', l_raw)
l_raw = re.sub('const ', '', l_raw)
l_raw = re.sub('struct ', '', l_raw)
if (len(l_raw) == 0): continue

View File

@ -14,7 +14,9 @@
# - 0x1000 | ~\.platformio\packages\framework-arduinoespressif32\tools\sdk\esp32\bin\bootloader_dout_40m.bin
# - 0x8000 | ~\Tasmota\.pio\build\<env name>\partitions.bin
# - 0xe000 | ~\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin
# - 0x10000 | ~\Tasmota\.pio\build\<env name>/firmware.bin
# - 0x10000 | ~\.platformio/packages/framework-arduinoespressif32/variants/tasmota/\<env name>-safeboot.bin
# - 0xe0000 | ~\Tasmota\.pio\build\<env name>/firmware.bin
# - 0x3b0000| ~\Tasmota\.pio\build\<env name>/littlefs.bin
Import("env")
@ -28,6 +30,7 @@ from os.path import join
import csv
import requests
import shutil
import subprocess
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
@ -35,9 +38,36 @@ import esptool
FRAMEWORK_DIR = platform.get_package_dir("framework-arduinoespressif32")
variants_dir = join(FRAMEWORK_DIR, "variants", "tasmota")
def esp32_fetch_safeboot_bin(chip):
safeboot_fw_url = "https://github.com/arendst/Tasmota-firmware/raw/main/firmware/tasmota32/tasmota" + ("32solo1" if "solo1" in env.subst("$BUILD_DIR") else chip[3:]) + "-safeboot.bin"
safeboot_fw_name = join(variants_dir,"tasmota" + ("32solo1" if "solo1" in env.subst("$BUILD_DIR") else chip[3:]) + "-safeboot.bin")
def esp32_create_chip_string(chip):
tasmota_platform = env.subst("$BUILD_DIR").split('/')[-1]
tasmota_platform = tasmota_platform.split('-')[0]
if 'tasmota' and chip[3:] not in tasmota_platform: # quick check for a valid name like 'tasmota' + '32c3'
print('Unexpected naming conventions in this build environment -> Undefined behavior for further build process!!')
return tasmota_platform
def esp32_build_filesystem(fs_size):
files = env.GetProjectOption("custom_files_upload").splitlines()
filesystem_dir = join(env.subst("$BUILD_DIR"),"littlefs_data")
if not os.path.exists(filesystem_dir):
os.makedirs(filesystem_dir)
print("Creating filesystem with content:")
for file in files:
if "no_files" in file:
continue
shutil.copy(file, filesystem_dir)
if not os.listdir(filesystem_dir):
print("No files added -> will NOT create littlefs.bin and NOT overwrite fs partition!")
return False
env.Replace( MKSPIFFSTOOL=platform.get_package_dir("tool-mklittlefs") + '/mklittlefs' )
tool = env.subst(env["MKSPIFFSTOOL"])
cmd = (tool,"-c",filesystem_dir,"-s",fs_size,join(env.subst("$BUILD_DIR"),"littlefs.bin"))
returncode = subprocess.call(cmd, shell=False)
# print(returncode)
return True
def esp32_fetch_safeboot_bin(tasmota_platform):
safeboot_fw_url = "https://github.com/arendst/Tasmota-firmware/raw/main/firmware/tasmota32/" + tasmota_platform + "-safeboot.bin"
safeboot_fw_name = join(variants_dir, tasmota_platform + "-safeboot.bin")
if(exists(safeboot_fw_name)):
print("safeboot binary already in place.")
return
@ -47,9 +77,9 @@ def esp32_fetch_safeboot_bin(chip):
open(safeboot_fw_name, "wb").write(response.content)
print("safeboot binary written to variants dir.")
def esp32_copy_new_safeboot_bin(chip,new_local_safeboot_fw):
def esp32_copy_new_safeboot_bin(tasmota_platform,new_local_safeboot_fw):
print("Copy new local safeboot firmware to variants dir -> using it for further flashing operations")
safeboot_fw_name = join(variants_dir,"tasmota" + ("32solo1" if "solo1" in env.subst("$BUILD_DIR") else chip[3:]) + "-safeboot.bin")
safeboot_fw_name = join(variants_dir, tasmota_platform + "-safeboot.bin")
if os.path.exists(variants_dir):
shutil.copy(new_local_safeboot_fw, safeboot_fw_name)
@ -58,9 +88,9 @@ def esp32_create_combined_bin(source, target, env):
# The offset from begin of the file where the app0 partition starts
# This is defined in the partition .csv file
factory_offset = -1 # error code value
# factory_offset = -1 # error code value - currently unused
app_offset = 0x10000 # default value for "old" scheme
spiffs_offset = -1 # error code value
fs_offset = -1 # error code value
with open(env.BoardConfig().get("build.partitions")) as csv_file:
print("Read partitions from ",env.BoardConfig().get("build.partitions"))
csv_reader = csv.reader(csv_file, delimiter=',')
@ -76,20 +106,22 @@ def esp32_create_combined_bin(source, target, env):
app_offset = int(row[3],base=16)
# elif(row[0] == 'factory'):
# factory_offset = int(row[3],base=16)
# elif(row[0] == 'spiffs'):
# spiffs_offset = int(row[3],base=16)
elif(row[0] == 'spiffs'):
if esp32_build_filesystem(row[4]):
fs_offset = int(row[3],base=16)
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
tasmota_platform = esp32_create_chip_string(chip)
if not os.path.exists(variants_dir):
os.makedirs(variants_dir)
if("safeboot" in firmware_name):
esp32_copy_new_safeboot_bin(chip,firmware_name)
esp32_copy_new_safeboot_bin(tasmota_platform,firmware_name)
else:
esp32_fetch_safeboot_bin(chip)
esp32_fetch_safeboot_bin(tasmota_platform)
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
cmd = [
"--chip",
@ -111,10 +143,32 @@ def esp32_create_combined_bin(source, target, env):
if("safeboot" not in firmware_name):
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
else:
print("Upload new safeboot binary only")
#print('Using esptool.py arguments: %s' % ' '.join(cmd))
if(fs_offset != -1):
fs_bin = join(env.subst("$BUILD_DIR"),"littlefs.bin")
if exists(fs_bin):
print(f" - {hex(fs_offset)}| {fs_bin}")
cmd += [hex(fs_offset), fs_bin]
env.Replace(
UPLOADERFLAGS=[
"--chip", chip,
"--port", '"$UPLOAD_PORT"',
"--baud", "$UPLOAD_SPEED",
"--before", "default_reset",
"--after", "hard_reset",
"write_flash", "-z",
"--flash_mode", "${__get_board_flash_mode(__env__)}",
"--flash_freq", "${__get_board_f_flash(__env__)}",
"--flash_size", flash_size
],
UPLOADCMD='"$PYTHONEXE" "$UPLOADER" $UPLOADERFLAGS ' + " ".join(cmd[7:])
)
print("Will use custom upload command for flashing operation to add file system defined for this build target.")
# print('Using esptool.py arguments: %s' % ' '.join(cmd))
esptool.main(cmd)

View File

@ -36,6 +36,7 @@ default_envs =
; tasmota32solo1
; tasmota32c3
; tasmota32s2
; tasmota32s3
; tasmota32-odroidgo
; tasmota32-core2
@ -90,8 +91,8 @@ lib_extra_dirs = ${library.lib_extra_dirs}
[env:tasmota32_base]
; *** Uncomment next lines ";" to enable development Tasmota Arduino version ESP32
;platform = https://github.com/tasmota/platform-espressif32/releases/download/v2.0.3rc1/platform-espressif32-2.0.3new.zip
;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/825/framework-arduinoespressif32-v4.4_work-c4b83228a5.tar.gz
;platform = https://github.com/tasmota/platform-espressif32/releases/download/v.2.0.3/platform-espressif32-v.2.0.3.zip
;platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/releases/download/837/framework-arduinoespressif32-v4.4_dev-6fa4526c0d.tar.gz
build_unflags = ${esp32_defaults.build_unflags}
build_flags = ${esp32_defaults.build_flags}

View File

@ -12,6 +12,14 @@ build_flags = ${env:tasmota32_base.build_flags}
-D USE_WIFI_RANGE_EXTENDER
-D USE_WIFI_RANGE_EXTENDER_NAPT
[env:tasmota32s3]
extends = env:tasmota32_base
board = esp32s3
build_flags = ${env:tasmota32_base.build_flags} -D FIRMWARE_TASMOTA32
; example for custom file upload in Tasmota Filesystem
; custom_files_upload = ${env:tasmota32_base.custom_files_upload}
; tasmota/berry/modules/Partition_wizard.tapp
; *** Debug version used for PlatformIO Home Project Inspection
[env:tasmota-debug]
build_type = debug
@ -52,25 +60,3 @@ debug_init_break = tbreak setup
build_unflags = ${core32solo1.build_unflags}
build_flags = ${core32solo1.build_flags}
monitor_filters = esp32_exception_decoder
; *** alpha S3 Version
[env:tasmota32s3]
extends = env:tasmota32_base
board = esp32s3
build_flags = ${env:tasmota32_base.build_flags} -D FIRMWARE_BLUETOOTH
lib_extra_dirs =
lib/lib_basic
lib/lib_ssl
lib/lib_i2c
lib/lib_rf
lib/lib_div
lib/lib_display
lib/lib_audio
lib/libesp32
lib/libesp32_lvgl
lib/libesp32_div
lib/libesp32_eink
lib_ignore =
TTGO TWatch Library
Micro-RTSP
epdiy

View File

@ -34,6 +34,11 @@ lib_ignore =
BluetoothSerial
; Disable next if you want to use ArduinoOTA in Tasmota32 (default disabled)
ArduinoOTA
; Add files to Filesystem for all env (global). Remove no files entry and add add a line with the file to include
; Example for adding the Partition Manager
; custom_files_upload =
; tasmota/berry/modules/Partition_Manager.tapp
custom_files_upload = no_files
[env:tasmota32]
extends = env:tasmota32_base
@ -105,6 +110,9 @@ lib_ignore =
TTGO TWatch Library
Micro-RTSP
epdiy
[env:tasmota32c3usb]
extends = env:tasmota32c3
board = esp32c3usb
[env:tasmota32s2]
extends = env:tasmota32_base
@ -176,6 +184,21 @@ lib_ignore =
Micro-RTSP
epdiy
[env:tasmota32c3usb-safeboot]
extends = env:tasmota32_base
board = esp32c3usb
build_unflags = ${env:tasmota32_base.build_unflags}
-flto
-mtarget-align
build_flags = ${env:tasmota32_base.build_flags} -DFIRMWARE_SAFEBOOT
-fno-lto
lib_extra_dirs = lib/lib_ssl, lib/libesp32
lib_ignore =
TTGO TWatch Library
NimBLE-Arduino
Micro-RTSP
epdiy
[env:tasmota32-AF]
extends = env:tasmota32_base
build_flags = ${env:tasmota32_base.build_flags} -DMY_LANGUAGE=af_AF -DFIRMWARE_TASMOTA32

View File

@ -1,2 +1,3 @@
# start Partition Manager
# rm Partition_Manager.tapp; zip Partition_Manager.tapp -j -0 Partition_Manager/*
import partition

Binary file not shown.

View File

@ -0,0 +1,2 @@
# rm Partition_wizard.tapp; zip -j -0 Partition_wizard.tapp Partition_Wizard/*
import partition_wizard

View File

@ -22,27 +22,154 @@ var partition = module('partition')
# } esp_partition_info_t_simplified;
#
#######################################################################
# class Partition_info
#
# def init(raw)
#
# # check if the parition is an OTA partition
# # if yes, return OTA number (starting at 0)
# # if no, return nil
# def is_ota()
#
# # # check if the parition is a SPIFFS partition
# # returns bool
# def is_spiffs()
#
# # get the actual image size give of the partition
# # returns -1 if the partition is not an app ota partition
# def get_image_size()
#
# def tostring()
#
# def tobytes()
#end
class Partition_info
var type
var subtype
var start
var size
var label
var flags
#- remove trailing NULL chars from a buffer before converting to string -#
#- Berry strings can contain NULL, but this messes up C-Berry interface -#
static def remove_trailing_zeroes(b)
while size(b) > 0
if b[-1] == 0 b.resize(size(b)-1)
else break end
end
return b
end
#
def init(raw)
if raw == nil || !issubclass(bytes, raw) # no payload, empty partition information
self.type = 0
self.subtype = 0
self.start = 0
self.size = 0
self.label = ''
self.flags = 0
return
end
#- we have a payload, parse it -#
var magic = raw.get(0,2)
if magic == 0x50AA #- partition entry -#
self.type = raw.get(2,1)
self.subtype = raw.get(3,1)
self.start = raw.get(4,4)
self.size = raw.get(8,4)
self.label = self.remove_trailing_zeroes(raw[12..27]).asstring()
self.flags = raw.get(28,4)
elif magic == 0xEBEB #- MD5 -#
else
import string
raise "internal_error", string.format("invalid magic number %02X", magic)
end
end
# check if the parition is an OTA partition
# if yes, return OTA number (starting at 0)
# if no, return nil
def is_ota()
if self.type == 0 && (self.subtype >= 0x10 && self.subtype < 0x20)
return self.subtype - 0x10
end
end
# check if the parition is a SPIFFS partition
# returns bool
def is_spiffs()
return self.type == 1 && self.subtype == 130
end
# get the actual image size give of the partition
# returns -1 if the partition is not an app ota partition
def get_image_size()
import flash
if self.is_ota() == nil return -1 end
try
var addr = self.start
var magic_byte = flash.read(addr, 1).get(0, 1)
if magic_byte != 0xE9 return -1 end
var seg_count = flash.read(addr+1, 1).get(0, 1)
# print("Segment count", seg_count)
var seg_offset = addr + 0x20 # sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) = 24 + 8
for seg_num:0..seg_count-1
# print(string.format("Reading 0x%08X", seg_offset))
var segment_header = flash.read(seg_offset - 8, 8)
var seg_start_addr = segment_header.get(0, 4)
var seg_size = segment_header.get(4,4)
# print(string.format("Segment %i: flash_offset=0x%08X start_addr=0x%08X size=0x%08X", seg_num, seg_offset, seg_start_addr, seg_size))
seg_offset += seg_size + 8 # add segment_length + sizeof(esp_image_segment_header_t)
end
var total_size = seg_offset - addr + 1 # add 1KB for safety
# print(string.format("Total size = %i KB", total_size/1024))
return total_size
except .. as e, m
print("BRY: Exception> '" + e + "' - " + m)
return -1
end
end
def tostring()
import string
var type_s = ""
var subtype_s = ""
if self.type == 0 type_s = "app"
if self.subtype == 0 subtype_s = "factory"
elif self.subtype >= 0x10 && self.subtype < 0x20 subtype_s = "ota" + str(self.subtype - 0x10)
elif self.subtype == 0x20 subtype_s = "test"
end
elif self.type == 1 type_s = "data"
if self.subtype == 0x00 subtype_s = "otadata"
elif self.subtype == 0x01 subtype_s = "phy"
elif self.subtype == 0x02 subtype_s = "nvs"
elif self.subtype == 0x03 subtype_s = "coredump"
elif self.subtype == 0x04 subtype_s = "nvskeys"
elif self.subtype == 0x05 subtype_s = "efuse_em"
elif self.subtype == 0x80 subtype_s = "esphttpd"
elif self.subtype == 0x81 subtype_s = "fat"
elif self.subtype == 0x82 subtype_s = "spiffs"
end
end
#- reformat strings -#
if type_s != "" type_s = " (" + type_s + ")" end
if subtype_s != "" subtype_s = " (" + subtype_s + ")" end
return string.format("<instance: Partition_info(%d%s,%d%s,0x%08X,0x%08X,'%s',0x%X)>",
self.type, type_s,
self.subtype, subtype_s,
self.start, self.size,
self.label, self.flags)
end
def tobytes()
#- convert to raw bytes -#
var b = bytes('AA50') #- set magic number -#
b.resize(32).resize(2) #- pre-reserve 32 bytes -#
b.add(self.type, 1)
b.add(self.subtype, 1)
b.add(self.start, 4)
b.add(self.size, 4)
var label = bytes().fromstring(self.label)
label.resize(16)
b = b + label
b.add(self.flags, 4)
return b
end
end
partition.Partition_info = Partition_info
#-------------------------------------------------------------
- OTA Data
@ -73,77 +200,302 @@ var partition = module('partition')
so current ota app sub type id is x , dest bin subtype is y,total ota app count is n
seq will add (x + n*1 + 1 - seq)%n
-------------------------------------------------------------#
# class Partition_otadata
# var maxota #- number of highest OTA partition, default 1 (double ota0/ota1) -#
# var offset #- offset of the otadata partition (0x2000 in length), default 0xE000 -#
# var active_otadata #- which otadata block is active, 0 or 1, i.e. 0xE000 or 0xF000 -#
# var seq0 #- ota_seq of first block -#
# var seq1 #- ota_seq of second block -#
#
# #- crc32 for ota_seq as 32 bits unsigned, with init vector -1 -#
# static def crc32_ota_seq(seq)
#
# #---------------------------------------------------------------------#
# # Rest of the class
# #---------------------------------------------------------------------#
# def init(maxota, offset)
#
# #- update ota_max, needs to recompute everything -#
# def set_ota_max(n)
#
# # change the active OTA partition
# def set_active(n)
#
# #- load otadata from SPI Flash -#
# def load()
#
# # Save partition information to SPI Flash
# def save()
#
# # Produce a human-readable representation of the object with relevant information
# def tostring()
#end
class Partition_otadata
var maxota #- number of highest OTA partition, default 1 (double ota0/ota1) -#
var offset #- offset of the otadata partition (0x2000 in length), default 0xE000 -#
var active_otadata #- which otadata block is active, 0 or 1, i.e. 0xE000 or 0xF000 -#
var seq0 #- ota_seq of first block -#
var seq1 #- ota_seq of second block -#
#-------------------------------------------------------------
- Simple CRC32 imple
-
- adapted from Python https://rosettacode.org/wiki/CRC-32#Python
-------------------------------------------------------------#
static def crc32_create_table()
var a = []
for i:0..255
var k = i
for j:0..7
if k & 1
k = (k >> 1) & 0x7FFFFFFF
k ^= 0xedb88320
else
k = (k >> 1) & 0x7FFFFFFF
end
end
a.push(k)
end
return a
end
static crc32_table = Partition_otadata.crc32_create_table()
static def crc32_update(buf, crc)
crc ^= 0xffffffff
for k:0..size(buf)-1
crc = (crc >> 8 & 0x00FFFFFF) ^ Partition_otadata.crc32_table[(crc & 0xff) ^ buf[k]]
end
return crc ^ 0xffffffff
end
#- crc32 for ota_seq as 32 bits unsigned, with init vector -1 -#
static def crc32_ota_seq(seq)
return Partition_otadata.crc32_update(bytes().add(seq, 4), 0xFFFFFFFF)
end
#---------------------------------------------------------------------#
# Rest of the class
#---------------------------------------------------------------------#
def init(maxota, offset)
self.maxota = maxota
if self.maxota == nil self.maxota = 1 end
self.offset = offset
if self.offset == nil self.offset = 0xE000 end
self.active_otadata = 0
self.load()
end
#- update ota_max, needs to recompute everything -#
def set_ota_max(n)
self.maxota = n
end
# change the active OTA partition
def set_active(n)
var seq_max = 0 #- current highest seq number -#
var block_act = 0 #- block number containing the highest seq number -#
if self.seq0 != nil
seq_max = self.seq0
block_act = 0
end
if self.seq1 != nil && self.seq1 > seq_max
seq_max = self.seq1
block_act = 1
end
#- compute the next sequence number -#
var actual_ota = (seq_max - 1) % (self.maxota + 1)
if actual_ota != n #- change only if different -#
if n > actual_ota seq_max += n - actual_ota
else seq_max += (self.maxota + 1) - actual_ota + n
end
#- update internal structure -#
if block_act == 1 #- current block is 1, so update block 0 -#
self.seq0 = seq_max
else #- or write to block 1 -#
self.seq1 = seq_max
end
self._validate()
end
end
#- load otadata from SPI Flash -#
def load()
import flash
var otadata0 = flash.read(0xE000, 32)
var otadata1 = flash.read(0xF000, 32)
self.seq0 = otadata0.get(0, 4) #- ota_seq for block 1 -#
self.seq1 = otadata1.get(0, 4) #- ota_seq for block 2 -#
var valid0 = otadata0.get(28, 4) == self.crc32_ota_seq(self.seq0) #- is CRC32 valid? -#
var valid1 = otadata1.get(28, 4) == self.crc32_ota_seq(self.seq1) #- is CRC32 valid? -#
if !valid0 self.seq0 = nil end
if !valid1 self.seq1 = nil end
self._validate()
end
#- internally used, validate data -#
def _validate()
self.active_otadata = 0 #- if none is valid, default to OTA0 -#
if self.seq0 != nil
self.active_otadata = (self.seq0 - 1) % (self.maxota + 1)
end
if self.seq1 != nil && (self.seq0 == nil || self.seq1 > self.seq0)
self.active_otadata = (self.seq1 - 1) % (self.maxota + 1)
end
end
# Save partition information to SPI Flash
def save()
import flash
#- check the block number to save, 0 or 1. Choose the highest ota_seq -#
var block_to_save = -1 #- invalid -#
var seq_to_save = -1 #- invalid value -#
# check seq0
if self.seq0 != nil
seq_to_save = self.seq0
block_to_save = 0
end
if (self.seq1 != nil) && (self.seq1 > seq_to_save)
seq_to_save = self.seq1
block_to_save = 1
end
# if none was good
if block_to_save < 0 block_to_save = 0 end
if seq_to_save < 0 seq_to_save = 1 end
var offset_to_save = self.offset + 0x1000 * block_to_save #- default 0xE000 or 0xF000 -#
var bytes_to_save = bytes()
bytes_to_save.add(seq_to_save, 4)
bytes_to_save += bytes("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
bytes_to_save.add(self.crc32_ota_seq(seq_to_save), 4)
#- erase flash area and write -#
flash.erase(offset_to_save, 0x1000)
flash.write(offset_to_save, bytes_to_save)
end
# Produce a human-readable representation of the object with relevant information
def tostring()
import string
return string.format("<instance: Partition_otadata(ota_active:%d, ota_seq=[%d,%d], ota_max=%d)>",
self.active_otadata, self.seq0, self.seq1, self.maxota)
end
end
partition.Partition_otadata = Partition_otadata
#-------------------------------------------------------------
- Class for a partition table entry
-------------------------------------------------------------#
#class Partition
# var raw #- raw bytes of the partition table in flash -#
# var md5 #- md5 hash of partition list -#
# var slots
# var otadata #- instance of Partition_otadata() -#
#
# def init()
#
# # Load partition information from SPI Flash
# def load()
#
# def get_ota_slot(n)
#
# #- compute the highest ota<x> partition -#
# def ota_max()
#
# def load_otadata()
#
# # get the active OTA app partition number
# def get_active()
#
# #- change the active partition -#
# def set_active(n)
#
# #- convert to human readble -#
# def tostring()
#
# #- convert the slots to raw bytes, ready to falsh to parition page -#
# def tobytes()
#
# #- write back to flash -#
# def save()
#
# #- invalidate SPIFFS partition to force format at next boot -#
# #- we simply erase the first byte of the first 2 blocks in the SPIFFS partition -#
# def invalidate_spiffs()
#end
class Partition
var raw #- raw bytes of the partition table in flash -#
var md5 #- md5 hash of partition list -#
var slots
var otadata #- instance of Partition_otadata() -#
def init()
self.slots = []
self.load()
self.parse()
self.load_otadata()
end
# Load partition information from SPI Flash
def load()
import flash
self.raw = flash.read(0x8000,0x1000)
end
#- parse the raw bytes to a structured list of partition items -#
def parse()
for i:0..94 # there are maximum 95 slots + md5 (0xC00)
var item_raw = self.raw[i*32..(i+1)*32-1]
var magic = item_raw.get(0,2)
if magic == 0x50AA #- partition entry -#
var slot = Partition_info(item_raw)
self.slots.push(slot)
elif magic == 0xEBEB #- MD5 -#
self.md5 = self.raw[i*32+16..i*33-1]
break
else
break
end
end
end
def get_ota_slot(n)
for slot: self.slots
if slot.is_ota() == n return slot end
end
return nil
end
#- compute the highest ota<x> partition -#
def ota_max()
var ota_max = 0
for slot:self.slots
if slot.type == 0 && (slot.subtype >= 0x10 && slot.subtype < 0x20)
var ota_num = slot.subtype - 0x10
if ota_num > ota_max ota_max = ota_num end
end
end
return ota_max
end
def load_otadata()
#- look for otadata partition offset, and max_ota -#
var otadata_offset = 0xE000 #- default value -#
var ota_max = self.ota_max()
for slot:self.slots
if slot.type == 1 && slot.subtype == 0 #- otadata -#
otadata_offset = slot.start
end
end
self.otadata = Partition_otadata(ota_max, otadata_offset)
end
# get the active OTA app partition number
def get_active()
return self.otadata.active_otadata
end
#- change the active partition -#
def set_active(n)
if n < 0 || n > self.ota_max() raise "value_error", "Invalid ota partition number" end
self.otadata.set_ota_max(self.ota_max()) #- update ota_max if it changed -#
self.otadata.set_active(n)
end
#- convert to human readble -#
def tostring()
var ret = "<instance: Partition([\n"
for slot: self.slots
ret += " "
ret += slot.tostring()
ret += "\n"
end
ret += "],\n "
ret += self.otadata.tostring()
ret += "\n)>"
return ret
end
#- convert the slots to raw bytes, ready to falsh to parition page -#
def tobytes()
if size(self.slots) > 95 raise "value_error", "Too many partiition slots" end
var b = bytes()
for slot: self.slots
b += slot.tobytes()
end
#- compute MD5 -#
var md5 = MD5()
md5.update(b)
#- add the last segment -#
b += bytes("EBEBFFFFFFFFFFFFFFFFFFFFFFFFFFFF")
b += md5.finish()
#- complete -#
return b
end
#- write back to flash -#
def save()
import flash
var b = self.tobytes()
#- erase flash area and write -#
flash.erase(0x8000, 0x1000)
flash.write(0x8000, b)
self.otadata.save()
end
#- invalidate SPIFFS partition to force format at next boot -#
#- we simply erase the first byte of the first 2 blocks in the SPIFFS partition -#
def invalidate_spiffs()
import flash
#- we expect the SPIFFS partition to be the last one -#
var spiffs = self.slots[-1]
if !spiffs.is_spiffs() raise 'value_error', 'No FS partition found' end
var b = bytes("00") #- flash memory: we can turn bits from '1' to '0' -#
flash.write(spiffs.start , b) #- block #0 -#
flash.write(spiffs.start + 0x1000, b) #- block #1 -#
end
end
partition.Partition = Partition
#################################################################################
# Partition_manager_UI
@ -159,7 +511,7 @@ class Partition_manager_UI
def web_add_button()
import webserver
webserver.content_send(
"<form id=but_part_mgr style='display: block;' action='part_mgr' method='get'><button>Partition Manager</button></form>")
"<form id=but_part_mgr style='display: block;' action='part_mgr' method='get'><button>Partition Manager</button></form><p></p>")
end
#- ---------------------------------------------------------------------- -#
@ -170,7 +522,7 @@ class Partition_manager_UI
import string
#- define `bdis` style for gray disabled buttons -#
webserver.content_send("<fieldset><style>.bdis{background:#888;}.bdis:hover{background:#888;}</style>")
webserver.content_send(string.format("<legend><b title='Start: 0x%03X 000'>&nbsp;%s%s</b></legend>",
webserver.content_send(string.format("<legend><b title='Start: 0x%03X 000'>&nbsp;%s%s&nbsp;</b></legend>",
slot.start / 0x1000, slot.label, active ? " (active)" : ""))
webserver.content_send(string.format("<p><b>Partition size: </b>%i KB</p>", slot.size / 1024))
@ -212,8 +564,8 @@ class Partition_manager_UI
def page_show_spiffs(slot, free_mem)
import webserver
import string
webserver.content_send(string.format("<fieldset><legend><b title='Start: 0x%03X 000'>&nbsp;%s</b></legend>",
slot.start / 0x1000, slot.label))
webserver.content_send(string.format("<fieldset><legend><b title='Start: 0x%03X 000'>&nbsp;filesystem&nbsp;</b></legend>",
slot.start / 0x1000))
webserver.content_send(string.format("<p><b>Partition size:</b> %i KB</p>", slot.size / 1024))
@ -226,8 +578,8 @@ class Partition_manager_UI
webserver.content_send("<hr><p><b>New size:</b> (multiple of 16 KB)</p>")
webserver.content_send("<form action='/part_mgr' method='post' ")
webserver.content_send("onsubmit='return confirm(\"This will DELETE the content of the file system and cause a restart.\");'>")
webserver.content_send(string.format("<input type='number' min='0' max='%d' step='16' name='spiffs_size' value='%i'>", (slot.size + free_mem) / 1024, ((slot.size + free_mem) / 1024 / 16)*16))
webserver.content_send("<p></p><button name='resize' class='button bred'>Resize SPIFFS</button></form></p>")
webserver.content_send(string.format("<input type='number' min='0' max='%d' step='16' name='fs_size' value='%i'>", (slot.size + free_mem) / 1024, ((slot.size + free_mem) / 1024 / 16)*16))
webserver.content_send("<p></p><button name='resize' class='button bred'>Resize filesystem</button></form></p>")
webserver.content_send("<p></p></fieldset><p></p>")
end
@ -272,7 +624,7 @@ class Partition_manager_UI
var flash_size_kb = tasmota.memory()['flash']
webserver.content_send("<p><b>Resize app Partitions.</b><br>It is highly recommended to set<br>both partition with the same size.<br>SPIFFS is adjusted accordinlgy.</p>")
webserver.content_send("<p><b>Resize app Partitions.</b><br>It is highly recommended to set<br>both partition with the same size.<br>Filesystem is adjusted accordinlgy.</p>")
webserver.content_send("<form action='/part_mgr' method='post' ")
webserver.content_send("onsubmit='return confirm(\"This will DELETE the content of the file system and cause a restart.\");'>")
@ -307,13 +659,13 @@ class Partition_manager_UI
webserver.content_send_style() #- send standard Tasmota styles -#
# webserver.content_send("<p style='width:340px;'><b style='color:#f56'>Warning:</b> This can brick your device. Don't use unless you know what you are doing.</p>")
webserver.content_send("<fieldset><legend><b>&nbsp;Partition Manager</b></legend><p></p>")
webserver.content_send("<fieldset><legend><b>&nbsp;Partition Manager&nbsp;</b></legend><p></p>")
webserver.content_send("<p style='width:320px;'><b style='color:#f56'>Warning:</b> This can brick your device.</p>")
self.page_show_partitions(p)
webserver.content_send("<p></p></fieldset><p></p>")
if p.otadata.maxota > 0
webserver.content_send("<fieldset><legend><b>&nbsp;Re-partition</b></legend><p></p>")
webserver.content_send("<fieldset><legend><b>&nbsp;Re-partition&nbsp;</b></legend><p></p>")
self.page_show_repartition_asym(p)
webserver.content_send("<p></p></fieldset><p></p>")
end
@ -359,19 +711,19 @@ class Partition_manager_UI
#---------------------------------------------------------------------#
# Resize the SPIFFS partition, generally to extend it to full free size
#---------------------------------------------------------------------#
elif webserver.has_arg("spiffs_size")
elif webserver.has_arg("fs_size")
#- SPIFFS size change -#
var spiffs_size_kb = int(webserver.arg("spiffs_size"))
var spiffs_size_kb = int(webserver.arg("fs_size"))
var spiffs_slot = p.slots[-1] # last slot
var spiffs_max_size = ((tasmota.memory()['flash'] - (spiffs_slot.start / 1024)) / 16) * 16
if spiffs_slot == nil || !spiffs_slot.is_spiffs() raise "value_error", "Last slot is not SPIFFS type" end
if spiffs_slot == nil || !spiffs_slot.is_spiffs() raise "value_error", "Last slot is not FS type" end
var flash_size_kb = tasmota.memory()['flash']
if spiffs_size_kb < 0 || spiffs_size_kb > spiffs_max_size
raise "value_error", string.format("Invalid spiffs_size %i, should be between 0 and %i", spiffs_size_kb, spiffs_max_size)
raise "value_error", string.format("Invalid fs_size %i, should be between 0 and %i", spiffs_size_kb, spiffs_max_size)
end
if spiffs_size_kb == spiffs_slot.size/1024 raise "value_error", "SPIFFS size unchanged, abort" end
if spiffs_size_kb == spiffs_slot.size/1024 raise "value_error", "FS size unchanged, abort" end
#- write the new SPIFFS partition size -#
spiffs_slot.size = spiffs_size_kb * 1024
@ -391,7 +743,7 @@ class Partition_manager_UI
var app1 = p.get_ota_slot(1)
var spiffs = p.slots[-1]
if !spiffs.is_spiffs() raise 'internal_error', 'No SPIFFS partition found' end
if !spiffs.is_spiffs() raise 'internal_error', 'No FS partition found' end
if app0 == nil || app1 == nil
raise "internal_error", "Unable to find partitions app0 and app1"
end
@ -433,7 +785,7 @@ class Partition_manager_UI
var app1 = p.get_ota_slot(1)
var spiffs = p.slots[-1]
if !spiffs.is_spiffs() raise 'internal_error', 'No SPIFFS partition found' end
if !spiffs.is_spiffs() raise 'internal_error', 'No FS partition found' end
if app0 == nil || app1 == nil
raise "internal_error", "Unable to find partitions app0 and app1"
end

View File

@ -0,0 +1,802 @@
#######################################################################
# Partition Wizard for ESP32 - ESP32C3 - ESP32S2
#
# use : `import partition_wizard`
#
# Provides low-level objects and a Web UI
# rm Partition_Wizard.tapp; zip Partition_Wizard.tapp -j -0 Partition_Wizard/*
#######################################################################
var partition_wizard = module('partition_wizard')
#################################################################################
# Partition_wizard_UI
#
# WebUI for the partition manager
#################################################################################
class Partition_wizard_UI
static app_size_min = 832 # Min OTA size - let's set it at a safe 896KB for minimal Tasmota32 with TLS
static app_size_max = 3072 # Max OTA size - (4096 - 896 - 128)
static _default_safeboot_URL = "https://raw.githubusercontent.com/arendst/Tasmota-firmware/main/firmware/tasmota32/tasmota32%s-safeboot.bin"
def init()
import persist
if persist.find("factory_migrate") == true
# remove marker to avoid bootloop if something goes wrong
persist.remove("factory_migrate")
persist.save()
# continue the migration process 5 seconds after Wifi is connected
def continue_after_5s()
tasmota.remove_rule("parwiz_5s") # first remove rule to avoid firing it again at Wifi reconnect
tasmota.set_timer(5000, /-> self.do_safeboot_partitioning()) # delay by 5 s
end
tasmota.add_rule("Wifi#Connected=1", continue_after_5s, "parwiz_5s")
end
end
def default_safeboot_URL()
import string
var arch_sub = tasmota.arch()
if arch_sub[0..4] == "esp32"
arch_sub = arch_sub[5..] # get the esp32 variant
end
return string.format(self._default_safeboot_URL, arch_sub)
end
# create a method for adding a button to the main menu
# the button 'Partition Wizard' redirects to '/part_wiz?'
def web_add_button()
import webserver
webserver.content_send(
"<form id=but_part_mgr style='display: block;' action='part_wiz' method='get'><button>Partition Wizard</button></form><p></p>")
end
#- ---------------------------------------------------------------------- -#
#- Get fs unallocated size
#- ---------------------------------------------------------------------- -#
def get_unallocated_k(p)
var last_slot = p.slots[-1]
if last_slot.is_spiffs()
# verify that last slot is filesystem
var flash_size_k = self.get_max_flash_size_k(p)
var partition_end_k = (last_slot.start + last_slot.size) / 1024 # last kb used for fs
if partition_end_k < flash_size_k
return flash_size_k - partition_end_k
end
end
return 0
end
#- ---------------------------------------------------------------------- -#
#- Get max fs start address when expanded to maximum
#- ---------------------------------------------------------------------- -#
def get_max_fs_start_k(p)
var last_slot = p.slots[-1]
if last_slot.is_spiffs() # verify that last slot is filesystem
# get end of previous partition slot
var last_app = p.slots[-2]
# round upper 64kB
var max_fs_start_k = 64 * (((last_app.start + last_app.get_image_size() + 1023) / 1024 + 63) / 64)
return max_fs_start_k
end
return 0
end
#- ---------------------------------------------------------------------- -#
#- Get max falsh size
#
# Takes into account that the flash size written may not be accurate
# and the flash chip may be larger
#- ---------------------------------------------------------------------- -#
def get_max_flash_size_k(p)
var flash_size_k = tasmota.memory()['flash']
var flash_size_real_k = tasmota.memory().find("flash_real", flash_size_k)
if (flash_size_k != flash_size_real_k) && self.get_flash_definition_sector(p) != nil
flash_size_k = flash_size_real_k # try to expand the flash size definition
end
return flash_size_k
end
#- ---------------------------------------------------------------------- -#
#- Resize flash definition if needed
#- ---------------------------------------------------------------------- -#
def resize_max_flash_size_k(p)
var flash_size_k = tasmota.memory()['flash']
var flash_size_real_k = tasmota.memory().find("flash_real", flash_size_k)
var flash_definition_sector = self.get_flash_definition_sector(p)
if (flash_size_k != flash_size_real_k) && flash_definition_sector != nil
import flash
import string
flash_size_k = flash_size_real_k # try to expand the flash size definition
var flash_def = flash.read(flash_definition_sector, 4)
var size_before = flash_def[3]
var flash_size_code
var flash_size_real_m = flash_size_real_k / 1024 # size in MB
if flash_size_real_m == 1 flash_size_code = 0x00
elif flash_size_real_m == 2 flash_size_code = 0x10
elif flash_size_real_m == 4 flash_size_code = 0x20
elif flash_size_real_m == 8 flash_size_code = 0x30
elif flash_size_real_m == 16 flash_size_code = 0x40
end
if flash_size_code != nil
# apply the update
var old_def = flash_def[3]
flash_def[3] = (flash_def[3] & 0x0F) | flash_size_code
flash.write(flash_definition_sector, flash_def)
tasmota.log(string.format("UPL: changing flash definition from 0x02X to 0x%02X", old_def, flash_def[3]), 3)
else
raise "internal_error", "wrong flash size "+str(flash_size_real_m)
end
end
end
#- ---------------------------------------------------------------------- -#
#- Get current fs size
#- ---------------------------------------------------------------------- -#
def get_cur_fs_size_k(p)
var last_slot = p.slots[-1]
if last_slot.is_spiffs() # verify that last slot is filesystem
return (last_slot.size + 1023) / 1024
end
return 0
end
#- ---------------------------------------------------------------------- -#
#- Get flash sector for flash chip definition
# It appears to be at 0x1000 for ESP32, but at 0x0000 for ESP32C3/S3
#
# returns offset of sector containing flash information
# or `nil` if not found
#
# Internally looks first at 0x0000 then at 0x1000 for magic byte
#- ---------------------------------------------------------------------- -#
def get_flash_definition_sector(p)
import flash
for i:0..1
var offset = i * 0x1000
if flash.read(offset, 1) == bytes('E9') return offset end
end
end
#- ---------------------------------------------------------------------- -#
#- Propose to resize FS to max if some memory in unallocated
#- ---------------------------------------------------------------------- -#
def show_resize_fs(p)
import webserver
import string
var unallocated = self.get_unallocated_k(p)
# if there is unallocated space, propose only to claim it
if unallocated > 0
webserver.content_send("<fieldset><legend><b>&nbsp;Resize FS to max&nbsp;</b></legend><p></p>")
webserver.content_send(string.format("<p>You can expand the file system by %i KB.<br>Its content will be lost.</p>", unallocated))
webserver.content_send("<form action='/part_wiz' method='post' ")
webserver.content_send("onsubmit='return confirm(\"This will DELETE the content of the file system and cause a restart.\");'>")
webserver.content_send("<p></p><button name='max_fs' class='button bred'>Resize FS to max</button></form></p>")
webserver.content_send("<p></p></fieldset><p></p>")
elif self.has_factory_layout(p)
# else propose to expand or shrink the file system
var max_fs_start_k = self.get_max_fs_start_k(p)
var flash_size_k = self.get_max_flash_size_k()
var fs_max_size_k = flash_size_k - max_fs_start_k
var current_fs_size_k = self.get_cur_fs_size_k(p)
#print(string.format(">>> max_fs_start_k=0x%X flash_size_k=0x%X fs_max_size_k=%i current_fs_size_k=%i", max_fs_start_k, flash_size_k, fs_max_size_k, current_fs_size_k))
if max_fs_start_k > 0 && fs_max_size_k > 64
webserver.content_send("<fieldset><legend><b>&nbsp;Resize FS&nbsp;</b></legend><p></p>")
webserver.content_send("<p>You can expand of shrink the file system.<br>Its content will be lost.</p>")
webserver.content_send("<form action='/part_wiz' method='post' ")
webserver.content_send("onsubmit='return confirm(\"This will DELETE the content of the file system and cause a restart.\");'>")
webserver.content_send(string.format("<input type='number' min='64' max='%d' step='64' name='fs_size' value='%i'>", fs_max_size_k, current_fs_size_k))
webserver.content_send("<p></p><button name='resize_fs' class='button bred'>Resize FS</button></form></p>")
webserver.content_send("<p></p></fieldset><p></p>")
end
end
end
#- ---------------------------------------------------------------------- -#
#- Tests for factory layout
#- ---------------------------------------------------------------------- -#
# Returns if the device already has a factory layout:
# devices has 1 factory partition
# device has at least 1 OTA partition
# last partition is FS
#
# returns true or false
def has_factory_layout(p)
return p.has_factory() && p.ota_max() != nil && p.slots[-1].is_spiffs()
end
#- ---------------------------------------------------------------------- -#
#- Tests for factory migration
#- ---------------------------------------------------------------------- -#
# Returns if the device is eligible for a migration to factory layout:
# devices has 2x OTA partitions
# device has no factory partition
#
# returns true or false
def factory_migrate_eligible(p)
if p.ota_max() <= 0 return false end # device does not have 2x OTA
if p.get_factory_slot() != nil return false end
if !p.slots[-1].is_spiffs() return false end
return true # device does not have factory partition
end
# ----------------------------------------------------------------------
# Step 1:
# - pre-condition:
# factory_migrate_eligible(p) returns true
# - DONE state:
# boot on `app1`
# - READY state:
# boot on `app0`
# - Needed steps:
# check that `app1` is large enough for firmware in `app0`
# copy `app0` to `app1`
# restart on `app1`
# set continuation marker in persist to continue migration process at next boto
#
# Returns:
# - false if READY
# - true if DONE
# - string if ERROR, indicating the error
def test_step_1(p)
import string
if !self.factory_migrate_eligible(p) return "not eligible to migration" end
var cur_part = p.otadata.active_otadata # -1=factory 0=ota_0 1=ota_1...
if cur_part == 1 return true end
if cur_part != 0 return string.format("active_otadata=%i", cur_part) end # unsupported configuration
# current partition is `app0`
# get size of firmware in `app0` and check if it fits on `app1`
var app0 = p.get_ota_slot(0)
var app1 = p.get_ota_slot(0)
var app0_firmware_size = (app0 != nil) ? app0.get_image_size() : -1
var app1_size = (app1 != nil) ? app1.size : -1
if app0_firmware_size < 0 || app1_size < 0 return "can't find app0/1 sizes" end
if app0_firmware_size >= app1_size return "`app1` is too small" end
return false
end
# ----------------------------------------------------------------------
# Step 2:
# - pre-condition:
# factory_migrate_eligible(p) returns true
# - DONE state:
# `safeboot` flashed to `app0`
# `safeboot` is smaller than 832KB
# - READY state:
# false `safeboot` to `app0`
# - Needed steps:
# get `safeboot` URL
# check that `app0` is large enough for `safeboot`
# check that `safeboot` is smaller than 832KB
# flash `safeboot` on `app0`
#
# Returns:
# - false if READY
# - true if DONE
# - string if ERROR, indicating the error
def test_step_2(p)
import string
if !self.factory_migrate_eligible(p) return "not eligible to migration" end
var app0 = p.get_ota_slot(0)
if app0.size < (self.app_size_min * 1024) return "`app0` is too small for `safeboot`" end
var app0_image_size = app0.get_image_size()
if (app0_image_size > 0) && (app0_image_size < (self.app_size_min * 1024)) return true end
return false
end
# ----------------------------------------------------------------------
# Step 3:
# - pre-condition:
# booted on `app1` and `safeboot` flashed to `app0`
# - DONE state:
# Partition map is:
# `factory` with `safeboot` flashed, sized to 832KB
# `app0` resized to take all the remaining size but empty
# - READY state:
# `app0` is flashed with `safeboot`
# - Needed steps:
# `app0` renamed to `safeboot`
# `app0` changed subtype to `factory`
# `app1` moved to right after `factory` and resized
# `app1` chaned subtype to `app0` and renamed `app0`
#
# Returns:
# - false if READY
# - true if DONE
# - string if ERROR, indicating the error
def test_step_3(p)
import string
if !self.factory_migrate_eligible(p) return "not eligible to migration" end
return false
# var app0 = p.get_ota_slot(0)
# if app0.get_image_size() > (self.app_size_min * 1024) return "`app0` is too small for `safeboot`" end
end
# ----------------------------------------------------------------------
# Step 4:
# - pre-condition:
#
# Returns:
# - false if READY
# - true if DONE
# - string if ERROR, indicating the error
def test_step_4(p)
import string
return false
# var app0 = p.get_ota_slot(0)
# if app0.get_image_size() > (self.app_size_min * 1024) return "`app0` is too small for `safeboot`" end
end
static def copy_ota(from_addr, to_addr, size)
import flash
import string
var size_left = size
var offset = 0
tasmota.log(string.format("UPL: Copy flash from 0x%06X to 0x%06X (size: %ikB)", from_addr, to_addr, size / 1024), 2)
while size_left > 0
var b = flash.read(from_addr + offset, 4096)
flash.erase(to_addr + offset, 4096)
flash.write(to_addr + offset, b, true)
size_left -= 4096
offset += 4096
if ((offset-4096) / 102400) < (offset / 102400)
tasmota.log(string.format("UPL: Progress %ikB", offset/1024), 3)
end
end
tasmota.log("UPL: done", 2)
end
def do_step_1(p)
var step1_state = self.test_step_1(p)
if step1_state == true return true end
if type(step1_state) == 'string)' raise "internal_error", step1_state end
# copy firmware frop `app0` to `app1`
var app0 = p.get_ota_slot(0)
var app1 = p.get_ota_slot(1)
var app0_size = app0.get_image_size()
if app0_size > app1.size raise "internal_error", "`app1` too small to copy firmware form `app0`" end
self.copy_ota(app0.start, app1.start, app0_size)
p.set_active(1)
p.save()
tasmota.log("UPL: restarting on `app1`", 2)
tasmota.cmd("Restart 1")
end
def do_step_2(p, safeboot_url)
import string
if safeboot_url == nil || safeboot_url == ""
safeboot_url = self.default_safeboot_URL()
tasmota.log("UPL: no `safeboot` URL, defaulting to "+safeboot_url, 2)
end
var step2_state = self.test_step_2(p)
if step2_state == true return true end
if type(step2_state) == 'string)' raise "internal_error", step2_state end
if safeboot_url == nil || size(safeboot_url) == 0 raise "internal_error", "wrong safeboot URL "+str(safeboot_url) end
var cl = webclient()
cl.begin(safeboot_url)
var r = cl.GET()
if r != 200 raise "network_error", "GET returned "+str(r) end
var safeboot_size = cl.get_size()
if safeboot_size <= 500000 raise "internal_error", "wrong safeboot size "+str(safeboot_size) end
if safeboot_size > (self.app_size_min * 1024) raise "internal_error", "safeboot is too large "+str(safeboot_size / 1024)+"kB" end
tasmota.log(string.format("UPL: flashing `safeboot` from %s %ikB", safeboot_url, (safeboot_size / 1024) + 1), 2)
var app0 = p.get_ota_slot(0)
if app0.start != 0x10000 raise "internal_error", "`app0` offset is not 0x10000" end
cl.write_flash(app0.start)
cl.close()
return true
end
def do_step_3(p)
import string
import flash
var step3_state = self.test_step_3(p)
if step3_state == true return true end
if type(step3_state) == 'string)' raise "internal_error", step3_state end
var app0 = p.get_ota_slot(0)
var app1 = p.get_ota_slot(1)
if app0 == nil || app1 == nil raise "internal_error", "there are no `app0` or `app1` partitions" end
var factory_size = self.app_size_min * 1024
var firm0_size = app0.get_image_size()
if firm0_size <= 0 raise "internal_error", "invalid size in app0 partition" end
if firm0_size >= factory_size raise "internal_error", "app0 partition is too big for factory" end
# do the change
app0.subtype = 0 # factory subtype
app0.size = factory_size
app0.label = 'safeboot'
app1.subtype = 0x10 # app1 becomes app0
app1.label = 'app0'
var f1_start = app1.start
app1.start = app0.start + factory_size
app1.size += f1_start - app1.start
# swicth partitions
p.set_active(0)
p.save()
p.switch_factory(true)
tasmota.cmd("Restart 1")
return true
end
# display the step state DONE/READY/ERROR with color and either step description or error message
# arg
# state: true=DONE, false=READY, string=ERROR with message
# msg: description if DONE or READY
# returns HTML string
def display_step_state(state, msg)
if state == true
return "<span style='color:#0F0'>DONE</span> "+msg
elif state == false
return "<span style='color:#FA0'>READY</span> "+msg
else
return "<span style='color:#F00'>ERROR</span> "+str(state)
end
end
#- ---------------------------------------------------------------------- -#
#- Show page to migrate to factory layout + single OTA
#- ---------------------------------------------------------------------- -#
def show_migrate_to_factory(p)
# display ota partitions
import webserver
import string
if !self.factory_migrate_eligible(p) return end
webserver.content_send("<fieldset><legend><b>&nbsp;Migrate to safeboot partition layout&nbsp;</b></legend><p></p>")
webserver.content_send("<p>The `safeboot` layout allows for increased size<br>of firmware or file-system.</p>")
webserver.content_send("<p>Please see <a href=''>Safeboot layout documentation</a></p>")
webserver.content_send("<p>&nbsp;</p>")
webserver.content_send(string.format("<p>Step 1: %s</p>", self.display_step_state(self.test_step_1(p), "boot on `app1`")))
webserver.content_send(string.format("<p>Step 2: %s</p>", self.display_step_state(self.test_step_2(p), "flash `safeboot` to `app0`")))
webserver.content_send(string.format("<p>Step 3: %s</p>", self.display_step_state(self.test_step_3(p), "change partition map")))
webserver.content_send(string.format("<p>Step 4: %s</p>", self.display_step_state(self.test_step_4(p), "flash final firmware")))
webserver.content_send("<form action='/part_wiz' method='post' ")
webserver.content_send("onsubmit='return confirm(\"This will causes multiple restarts.\");'>")
var ota_url = tasmota.cmd("OtaUrl").find("OtaUrl", "")
webserver.content_send(string.format("<br><b>OTA Url</b><br><input id='o1' placeholder='OTA_URL' value='%s'><br>",
ota_url))
import persist
var safeboot_url = persist.find("safeboot_url", self.default_safeboot_URL())
webserver.content_send(string.format("<br><b>SAFEBOOT Url</b> (don't change)<input id='o2' placeholder='SAFEBOOT_URL' value='%s'><br>",
safeboot_url))
webserver.content_send("<p></p><button name='factory' class='button bred'>Start migration</button></form></p>")
webserver.content_send("<p></p></fieldset><p></p>")
end
#- ---------------------------------------------------------------------- -#
#- Show each partition one after the other - only OTA and SPIFFS
#- ---------------------------------------------------------------------- -#
def show_current_partitions(p)
# display ota partitions
import webserver
import string
var cur_part = p.otadata.active_otadata # -1=factory 0=ota_0 1=ota_1...
webserver.content_send("<fieldset><legend><b>&nbsp;Current partitions&nbsp;</b></legend><p></p><table>")
# don't show <sys> portion
#webserver.content_send("<tr><td title='offset:0x000000 size:0x010000'>&lt;sys&gt;:&nbsp;</td><td align='right'> 64 KB</td></tr>")
for slot:p.slots
var is_ota = slot.is_ota()
var is_factory = slot.is_factory()
if (is_ota != nil) || is_factory # display only partitions with app type
var current_boot_partition = (is_ota == cur_part) || (is_factory && cur_part == -1)
var usage_str = "unknown"
var used = slot.get_image_size()
if (used >= 0) && (used <= slot.size)
usage_str = string.format("used %i%%", ((used / 1024) * 100) / (slot.size / 1024))
end
var title = string.format("%ssubtype:%s offset:0x%06X size:0x%06X", current_boot_partition ? "booted " : "", slot.subtype_to_string(), slot.start, slot.size)
var col_before = ""
var col_after = ""
if current_boot_partition
col_before = "<span style='color:#0F0'>["
col_after = "]</span>"
end
# webserver.content_send(string.format("<p><b>%s</b> [%s]: %i KB (%s)</p>", slot.label, slot.subtype_to_string(), slot.size / 1024, usage_str))
webserver.content_send(string.format("<tr><td title='%s'><b>%s%s%s</b>:&nbsp;</td><td align='right'> %i KB </td><td>&nbsp;(%s)</td></tr>",
title, col_before, slot.label, col_after, slot.size / 1024, usage_str))
elif slot.is_spiffs()
# spiffs partition
var title = string.format("subtype:%s offset:0x%06X size:0x%06X", slot.subtype_to_string(), slot.start, slot.size)
webserver.content_send(string.format("<tr><td title='%s'><b>fs</b>:&nbsp;</td><td align='right'> %i KB</td></tr>", title, slot.size / 1024))
end
end
var unallocated = self.get_unallocated_k(p)
if unallocated > 0
var last_slot = p.slots[-1]
# verify that last slot is file-system
var partition_end_k = (last_slot.start + last_slot.size) / 1024 # last kb used for fs
webserver.content_send(string.format("<tr><td title='offset:0x%06X size:0x%06X'>&lt;free&gt;:&nbsp;</td><td align='right'> %i KB</td></tr>",
partition_end_k * 1024, unallocated * 1024, unallocated))
end
webserver.content_send("</table>")
# display if layout is factory
if self.has_factory_layout(p)
webserver.content_send("<p>This device uses the <b>safeboot</b> layout</p>")
end
webserver.content_send("</fieldset><p></p>")
end
#######################################################################
# Display the complete page
#######################################################################
def page_part_mgr()
import webserver
import string
import partition_core
if !webserver.check_privileged_access() return nil end
var p = partition_core.Partition() # load partition layout
webserver.content_start("Partition Wizard") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -#
if webserver.has_arg("wait")
webserver.content_send("<p>Migration process will start in 5 seconds<br>Magic is happening, leave device alone for 3 minutes.</p>")
webserver.content_button(webserver.BUTTON_MAIN) #- button back to main page -#
else
webserver.content_send("<p style='width:320px;'><b style='color:#f56'>Warning:</b> actions below can brick your device.</p>")
self.show_current_partitions(p)
self.show_resize_fs(p)
# show page for migration to factory
self.show_migrate_to_factory(p)
webserver.content_button(webserver.BUTTON_MANAGEMENT) #- button back to management page -#
end
webserver.content_stop() #- end of web page -#
end
#######################################################################
# Web Controller, called by POST to `/part_wiz`
#######################################################################
def page_part_ctl()
import webserver
if !webserver.check_privileged_access() return nil end
import string
import partition_core
import persist
#- check that the partition is valid -#
var p = partition_core.Partition()
try
#---------------------------------------------------------------------#
# Resize FS to max
#---------------------------------------------------------------------#
if webserver.has_arg("max_fs")
var unallocated = self.get_unallocated_k(p)
if unallocated <= 0 raise "value_error", "FS already at max size" end
self.resize_max_flash_size_k(p) # resize if needed
# since unallocated succeeded, we know the last slot is FS
var fs_slot = p.slots[-1]
fs_slot.size += unallocated * 1024
p.save()
p.invalidate_spiffs() # erase SPIFFS or data is corrupt
#- and force restart -#
webserver.redirect("/?rst=")
#---------------------------------------------------------------------#
# Resize FS to arbitrary size
#---------------------------------------------------------------------#
elif webserver.has_arg("resize_fs")
if !self.has_factory_layout(p) raise "internal_error", "Device does not avec safeboot layout" end
var fs = p.slots[-1]
var last_app = p.slots[-2]
if (last_app.get_image_size() <= 0) raise "internal_error", "last `app` partition has no firmware" end
var max_fs_start_k = self.get_max_fs_start_k(p)
var flash_size_k = self.get_max_flash_size_k(p)
var fs_max_size_k = flash_size_k - max_fs_start_k
var current_fs_size_k = self.get_cur_fs_size_k(p)
var fs_target = int(webserver.arg("fs_size"))
if (fs_target < 64) || (fs_target > fs_max_size_k) raise "value_error", string.format("Invalid FS #%d", fs_target) end
# apply the change
# shrink last OTA App
var delta = (fs_target * 1024) - fs.size
last_app.size -= delta
# move fs
fs.start -= delta
fs.size += delta
p.save()
p.invalidate_spiffs()
#- and force restart -#
webserver.redirect("/?rst=")
#---------------------------------------------------------------------#
# Switch OTA partition from one to another
#---------------------------------------------------------------------#
elif webserver.has_arg("factory")
var ota_url = webserver.arg("o1")
var safeboot_url = webserver.arg("o2")
if safeboot_url != nil && safeboot_url != ""
persist.safeboot_url = safeboot_url
persist.save()
end
if ota_url != nil && ota_url != ""
tasmota.cmd("OtaUrl "+ota_url)
end
tasmota.set_timer(5000, /-> self.do_safeboot_partitioning())
webserver.redirect("/part_wiz?wait=")
else
raise "value_error", "Unknown command"
end
except .. as e, m
tasmota.log(string.format("BRY: Exception> '%s' - %s", e, m), 2)
#- display error page -#
webserver.content_start("Parameter error") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -#
webserver.content_send(string.format("<p style='width:340px;'><b>Exception:</b><br>'%s'<br>%s</p>", e, m))
# webserver.content_send("<p></p></fieldset><p></p>")
webserver.content_button(webserver.BUTTON_MANAGEMENT) #- button back to management page -#
webserver.content_stop() #- end of web page -#
end
end
#---------------------------------------------------------------------#
# Apply the repartitioning process
#---------------------------------------------------------------------#
# returns:
# `true`: already completed
# `false`: in progress
# string: error with description of error
def do_safeboot_partitioning()
import webserver
import string
import partition_core
var p = partition_core.Partition() # load partition layout
if !self.factory_migrate_eligible(p) return true end
# STEP 1
var step1_state = self.test_step_1(p)
if type(step1_state) == 'string' return step1_state end
if step1_state == false
import persist
tasmota.log("UPL: Starting step 1", 2)
try
self.do_step_1(p)
except .. as e, m
tasmota.log(string.format("UPL: error (%s) %s", e, m), 2)
return m
end
persist.factory_migrate = true
persist.save()
return false
end
tasmota.log("UPL: Step 1 Done", 2)
# STEP 2
var step2_state = self.test_step_2(p)
if type(step2_state) == 'string' return step2_state end
if step2_state == false
tasmota.log("UPL: Starting step 2", 2)
import persist
var safeboot_url = persist.find("safeboot_url")
try
self.do_step_2(p, safeboot_url)
except .. as e, m
tasmota.log(string.format("UPL: error (%s) %s", e, m), 2)
return m
end
end
tasmota.log("UPL: Step 2 Done", 2)
# STEP 3
var step3_state = self.test_step_3(p)
if type(step3_state) == 'string' return step3_state end
if step3_state == false
tasmota.log("UPL: Starting step 3", 2)
try
self.do_step_3(p)
except .. as e, m
tasmota.log(string.format("UPL: error (%s) %s", e, m), 2)
return m
end
end
tasmota.log("UPL: Step 3 Done", 2)
# STEP 4
# Nothing to do, it's automatic
return false
end
#- ---------------------------------------------------------------------- -#
# respond to web_add_handler() event to register web listeners
#- ---------------------------------------------------------------------- -#
#- this is called at Tasmota start-up, as soon as Wifi/Eth is up and web server running -#
def web_add_handler()
import webserver
#- we need to register a closure, not just a function, that captures the current instance -#
webserver.on("/part_wiz", / -> self.page_part_mgr(), webserver.HTTP_GET)
webserver.on("/part_wiz", / -> self.page_part_ctl(), webserver.HTTP_POST)
end
end
partition_wizard.Partition_wizard_UI = Partition_wizard_UI
#- create and register driver in Tasmota -#
if tasmota
import partition_core
var partition_wizard_ui = partition_wizard.Partition_wizard_UI()
tasmota.add_driver(partition_wizard_ui)
## can be removed if put in 'autoexec.bat'
partition_wizard_ui.web_add_handler()
end
return partition_wizard
#- Example
import partition
# read
p = partition.Partition()
print(p)
-#

Binary file not shown.

View File

@ -0,0 +1,5 @@
# generate all openhasp tapp files
rm openhasp.tapp; zip -j -0 openhasp.tapp openhasp_core/*
rm openhasp_demo.tapp ; zip -j -0 openhasp_demo.tapp openhasp_demo/* openhasp_core/openhasp.be
cp openhasp.tapp ../openhasp
cp openhasp_demo.tapp ../openhasp

View File

@ -71,8 +71,8 @@ class lvh_obj
"w": "width",
"h": "height",
# arc
"asjustable": nil,
"mode": nil,
# "asjustable": nil,
# "mode": nil,
"start_angle": "bg_start_angle",
"start_angle1": "start_angle",
"end_angle": "bg_end_angle",
@ -81,13 +81,13 @@ class lvh_obj
"border_side": "style_border_side",
"border_width": "style_border_width",
"border_color": "style_border_color",
"line_width": nil, # depends on class
"line_width1": nil, # depends on class
"action": nil, # store the action in self.action
"hidden": nil, # apply to self
"enabled": nil, # apply to self
"click": nil, # synonym to enabled
"toggle": nil,
# "line_width": nil, # depends on class
# "line_width1": nil, # depends on class
# "action": nil, # store the action in self.action
# "hidden": nil, # apply to self
# "enabled": nil, # apply to self
# "click": nil, # synonym to enabled
# "toggle": nil,
"bg_color": "style_bg_color",
"bg_opa": "style_bg_opa",
"bg_grad_color": "style_bg_grad_color",
@ -98,48 +98,50 @@ class lvh_obj
"pad_top": "style_pad_top",
"pad_bottom": "style_pad_bottom",
"pad_all": "style_pad_all", # write-only
"type": nil,
# "type": nil,
# below automatically create a sub-label
"text": nil, # apply to self
"value_str": nil, # synonym to 'text'
"align": nil,
"text_font": nil,
"value_font": nil, # synonym to text_font
"text_color": nil,
"value_color": nil, # synonym to text_color
"value_ofs_x": nil,
"value_ofs_y": nil,
# "text": nil, # apply to self
# "value_str": nil, # synonym to 'text'
# "align": nil,
# "text_font": nil,
# "value_font": nil, # synonym to text_font
# "text_color": nil,
# "value_color": nil, # synonym to text_color
# "value_ofs_x": nil,
# "value_ofs_y": nil,
#
"min": nil,
"max": nil,
"val": "value",
# "min": nil,
# "max": nil,
# "val": nil,
"rotation": "rotation",
# img
"src": "src",
"image_recolor": "style_img_recolor",
"image_recolor_opa": "style_img_recolor_opa",
# spinner
"angle": nil,
"speed": nil,
# "angle": nil,
# "speed": nil,
# padding of knob
"pad_top2": nil,
"pad_bottom2": nil,
"pad_left2": nil,
"pad_right2": nil,
"pad_all2": nil,
"radius2": nil,
# "pad_top2": nil,
# "pad_bottom2": nil,
# "pad_left2": nil,
# "pad_right2": nil,
# "pad_all2": nil,
# "radius2": nil,
# rule based update of attributes
# supporting both `val` and `text`
"val_rule": nil,
"val_rule_formula": nil,
"text_rule": nil,
"text_rule_formula": nil,
"text_rule_format": nil,
# "val_rule": nil,
# "val_rule_formula": nil,
# "text_rule": nil,
# "text_rule_formula": nil,
# "text_rule_format": nil,
# roller
# "options": nil,
# qrcode
"qr_size": nil,
"qr_dark_color": nil,
"qr_light_color": nil,
"qr_text": nil,
# "qr_size": nil,
# "qr_dark_color": nil,
# "qr_light_color": nil,
# "qr_text": nil,
}
#====================================================================
@ -173,6 +175,21 @@ class lvh_obj
return bool(re.search("color$", str(t)))
end
#- remove trailing NULL chars from a bytes buffer before converting to string -#
#- Berry strings can contain NULL, but this messes up C-Berry interface -#
static def remove_trailing_zeroes(b)
var sz = size(b)
var i = 0
while i < sz
if b[-1-i] != 0 break end
i += 1
end
if i > 0
b.resize(size(b)-i)
end
return b
end
#################################################################################
# Parses a color attribute
#
@ -317,9 +334,24 @@ class lvh_obj
if event_hasp != nil
import string
var val = string.format('{"hasp":{"p%ib%i":"%s"}}', self._page._page_id, self.id, event_hasp)
var tas_event_more = "" # complementary data
if event.code == lv.EVENT_VALUE_CHANGED
try
# try to get the new val
var val = self.val
if val != nil tas_event_more = string.format(',"val":%i', val) end
var text = self.text
if text != nil
import json
tas_event_more += ',"text":'
tas_event_more += json.dump(text)
end
except ..
end
end
var tas_event = string.format('{"hasp":{"p%ib%i":{"event":"%s"%s}}}', self._page._page_id, self.id, event_hasp, tas_event_more)
# print("val=",val)
tasmota.set_timer(0, /-> tasmota.publish_rule(val))
tasmota.set_timer(0, /-> tasmota.publish_rule(tas_event))
end
end
@ -611,6 +643,12 @@ class lvh_obj
def get_pad_all()
end
def set_val(t)
self._lv_obj.set_value(t)
end
def get_val()
return self._lv_obj.get_value()
end
#====================================================================
# `radius2`
#====================================================================
@ -635,37 +673,35 @@ class lvh_obj
#- ------------------------------------------------------------#
def member(k)
import string
# ignore attributes
# print("member","self=",self,"k=",k)
import introspect
# print("> getmember", k)
var prefix = k[0..3]
if prefix == "set_" || prefix == "get_" return end
# if attribute name is in ignore list, abort
if self._attr_ignore.find(k) != nil return end
# check if the key is known
# first check if there is a method named `get_X()`
var f = introspect.get(self, "get_" + k) # call self method
if type(f) == 'function'
return f(self)
end
# next check if there is a mapping to an LVGL attribute
if self._attr_map.contains(k)
# attribute is known
# kv: (if string) the LVGL attribute name of the object - direct mapping
# kv: (if `nil`) call `get_<kv>` method of the object
import introspect
var kv = self._attr_map[k]
if kv == nil
# call the object's `get_X()`
var f = introspect.get(self, "get_" + k) # call self method
if type(f) == 'function'
return f(self)
end
else
# call the native LVGL object method
var f = introspect.get(self._lv_obj, "get_" + kv)
if type(f) == 'function' # found and function, call it
if string.find(kv, "style_") == 0
# style function need a selector as second parameter
return f(self._lv_obj, 0 #- lv.PART_MAIN | lv.STATE_DEFAULT -#)
else
return f(self._lv_obj)
end
f = introspect.get(self._lv_obj, "get_" + kv)
if type(f) == 'function' # found and function, call it
if string.find(kv, "style_") == 0
# style function need a selector as second parameter
return f(self._lv_obj, 0 #- lv.PART_MAIN | lv.STATE_DEFAULT -#)
else
return f(self._lv_obj)
end
end
end
# fallback to exception if attribute unknown or not a function
raise "value_error", "unknown attribute " + str(k)
end
@ -674,45 +710,44 @@ class lvh_obj
# `setmember` virtual setter
#- ------------------------------------------------------------#
def setmember(k, v)
# print(">> setmember", k, v)
# print(">>", classname(self), self._attr_map)
# ignore attributes
import string
import introspect
# print("> setmember", k, v)
var prefix = k[0..3]
if prefix == "set_" || prefix == "get_" return end
# if attribute name is in ignore list, abort
if self._attr_ignore.find(k) != nil return end
# is attribute known
# first check if there is a method named `set_X()`
var f = introspect.get(self, "set_" + k)
if type(f) == 'function'
f(self, v)
return
end
# next check if there is a mapping to an LVGL attribute
if self._attr_map.contains(k)
import string
import introspect
var kv = self._attr_map[k]
# if a string is attached to the name, then set the corresponding LVGL attribute
if kv
var f = introspect.get(self._lv_obj, "set_" + kv)
# if the attribute contains 'color', convert to lv_color
if type(kv) == 'string' && self.is_color_attribute(kv)
v = self.parse_color(v)
end
# print("f=", f, v, kv, self._lv_obj, self)
if type(f) == 'function'
if string.find(kv, "style_") == 0
# style function need a selector as second parameter
f(self._lv_obj, v, 0 #- lv.PART_MAIN | lv.STATE_DEFAULT -#)
else
f(self._lv_obj, v)
end
return
else
print("HSP: Could not find function set_"+kv)
end
else
# else call the specific method from self
var f = introspect.get(self, "set_" + k)
# print("f==",f)
if type(f) == 'function'
f(self, v)
return
end
end
var kv = self._attr_map[k]
f = introspect.get(self._lv_obj, "set_" + kv)
# if the attribute contains 'color', convert to lv_color
if self.is_color_attribute(kv)
v = self.parse_color(v)
end
# print("f=", f, v, kv, self._lv_obj, self)
if type(f) == 'function'
if string.find(kv, "style_") == 0
# style function need a selector as second parameter
f(self._lv_obj, v, 0 #- lv.PART_MAIN | lv.STATE_DEFAULT -#)
else
f(self._lv_obj, v)
end
return
else
print("HSP: Could not find function set_"+kv)
end
else
print("HSP: unknown attribute:", k)
end
@ -987,6 +1022,116 @@ class lvh_qrcode : lvh_obj
def get_qr_text() end
end
#====================================================================
# slider
#====================================================================
class lvh_slider : lvh_obj
static _lv_class = lv.slider
def set_val(t)
self._lv_obj.set_value(t, 0) # add second parameter - no animation
end
end
#====================================================================
# roller
#====================================================================
class lvh_roller : lvh_obj
static _lv_class = lv.roller
def set_val(t)
self._lv_obj.set_selected(t, 0) # add second parameter - no animation
end
def get_val()
return self._lv_obj.get_selected()
end
def set_options(t)
self._lv_obj.set_options(t, lv.ROLLER_MODE_NORMAL)
end
def get_options()
return self._lv_obj.get_options()
end
def set_text(t)
raise "attribute_error", "set_text unsupported on roller"
end
def get_text()
# allocate a bytes buffer
var b = bytes().resize(256) # force 256 bytes
self._lv_obj.get_selected_str(b._buffer(), 256)
b = self.remove_trailing_zeroes(b)
return b.asstring()
end
end
#====================================================================
# dropdown
#====================================================================
class lvh_dropdown : lvh_obj
static _lv_class = lv.dropdown
static _dir = [ lv.DIR_BOTTOM, lv.DIR_TOP, lv.DIR_LEFT, lv.DIR_RIGHT ] # 0 = down, 1 = up, 2 = left, 3 = right
def set_val(t)
self._lv_obj.set_selected(t, 0) # add second parameter - no animation
end
def get_val()
return self._lv_obj.get_selected()
end
def set_options(t)
self._lv_obj.set_options(t, lv.ROLLER_MODE_NORMAL)
end
def get_options()
return self._lv_obj.get_options()
end
def set_text(t)
# set_text sets a static text displayed whatever the value
# use `nil` to set back the text of the selected value
self._lv_obj.set_text(t)
end
def get_text()
var static_text = self._lv_obj.get_text()
if static_text == nil
# allocate a bytes buffer
var b = bytes().resize(256) # force 256 bytes
self._lv_obj.get_selected_str(b._buffer(), 256)
b = self.remove_trailing_zeroes(b)
return b.asstring()
else
return static_text
end
end
# direction needs a conversion from OpenHASP numbers and LVGL's
def set_direction(t)
# 0 = down, 1 = up, 2 = left, 3 = right
self._lv_obj.set_dir(self._dir[int(t)])
end
def get_direction()
var dir = self._lv_obj.get_dir()
var i = 0
while i < size(self._dir)
if dir == self._dir[i] return i end
i += 1
end
return -1
end
# show_selected (bool) is a OpenHASP addition
# only meaningful if set to `true`, setting to false requires a call to `set_text`
def set_show_selected(t)
if t
self._lv_obj.set_text(nil) # undo static text
end
end
def get_show_selected()
var static_text = self._lv_obj.get_text()
return (static_text == nil)
end
end
#################################################################################
#
# All other subclasses than just map the LVGL object
@ -997,10 +1142,7 @@ class lvh_bar : lvh_obj static _lv_class = lv.bar end
class lvh_btn : lvh_obj static _lv_class = lv.btn end
class lvh_btnmatrix : lvh_obj static _lv_class = lv.btnmatrix end
class lvh_checkbox : lvh_obj static _lv_class = lv.checkbox end
class lvh_dropdown : lvh_obj static _lv_class = lv.dropdown end
class lvh_line : lvh_obj static _lv_class = lv.line end
class lvh_roller : lvh_obj static _lv_class = lv.roller end
class lvh_slider : lvh_obj static _lv_class = lv.slider end
class lvh_textarea : lvh_obj static _lv_class = lv.textarea end
# special case for scr (which is actually lv_obj)
class lvh_scr : lvh_obj static _lv_class = nil end # no class for screen

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Wagwoord"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Poort"
#define D_POWER_FACTOR "Krag faktor"
#define D_POWERUSAGE "Krag"
@ -183,6 +184,7 @@
#define D_UPGRADE "opgradeer"
#define D_UPLOAD "Laai op"
#define D_UPTIME "Uptyd"
#define D_USED "used"
#define D_USER "Gebruiker"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Indeks"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Парола"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Порт"
#define D_POWER_FACTOR "Фактор на мощността"
#define D_POWERUSAGE "Мощност"
@ -183,6 +184,7 @@
#define D_UPGRADE "Обновяване"
#define D_UPLOAD "Качването е"
#define D_UPTIME "Време на работа"
#define D_USED "used"
#define D_USER "Потребител"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Индекс на UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Heslo"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Účiník"
#define D_POWERUSAGE "Příkon"
@ -183,6 +184,7 @@
#define D_UPGRADE "aktualizace"
#define D_UPLOAD "Nahrání..."
#define D_UPTIME "Uptime"
#define D_USED "used"
#define D_USER "Uživatel"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Passwort"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Leistungsfaktor"
#define D_POWERUSAGE "Leistung"
@ -183,6 +184,7 @@
#define D_UPGRADE "update"
#define D_UPLOAD "Upload"
#define D_UPTIME "Laufzeit"
#define D_USED "used"
#define D_USER "Benutzer"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV-Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Κωδικός"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Θύρα"
#define D_POWER_FACTOR "Συντελεστής Ισχύος"
#define D_POWERUSAGE "Ισχύς"
@ -183,6 +184,7 @@
#define D_UPGRADE "αναβάθμιση"
#define D_UPLOAD "Ανέβασμα"
#define D_UPTIME "Χρόνος λειτουργίας"
#define D_USED "used"
#define D_USER "Χρήστης"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Δείκτης UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Password"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Power Factor"
#define D_POWERUSAGE "Power"
@ -183,6 +184,7 @@
#define D_UPGRADE "upgrade"
#define D_UPLOAD "Upload"
#define D_UPTIME "Uptime"
#define D_USED "used"
#define D_USER "User"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Clave"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Puerto"
#define D_POWER_FACTOR "Factor de Potencia"
#define D_POWERUSAGE "Potencia"
@ -183,6 +184,7 @@
#define D_UPGRADE "Actualización"
#define D_UPLOAD "Carga"
#define D_UPTIME "Tiempo Encendido"
#define D_USED "used"
#define D_USER "Usuario"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Índice UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Mot de passe"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Fact de puiss"
#define D_POWERUSAGE "Puissance"
@ -183,6 +184,7 @@
#define D_UPGRADE "la mise à jour" // "Lancer la mise à jour"
#define D_UPLOAD "Upload" // Not better in french
#define D_UPTIME "Durée d'activité"
#define D_USED "used"
#define D_USER "Utilisateur"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Indice UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Wachtwurd"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Poort"
#define D_POWER_FACTOR "Krêftfaktor"
#define D_POWERUSAGE "Krêft"
@ -183,6 +184,7 @@
#define D_UPGRADE "upgrade"
#define D_UPLOAD "Stjoere"
#define D_UPTIME "Betjenstiid"
#define D_USED "used"
#define D_USER "Brûker"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV-index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "סיסמא"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "פורט"
#define D_POWER_FACTOR "גורם כוח"
#define D_POWERUSAGE "כוח"
@ -183,6 +184,7 @@
#define D_UPGRADE "שדרוג"
#define D_UPLOAD "העלאה"
#define D_UPTIME "זמן עליה"
#define D_USED "used"
#define D_USER "משתמש"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV אינדקס"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Jelszó"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Teljesítménytényező"
#define D_POWERUSAGE "Energiafelhasználás"
@ -183,6 +184,7 @@
#define D_UPGRADE "Frissítés"
#define D_UPLOAD "Feltöltés"
#define D_UPTIME "Üzemidő"
#define D_USED "used"
#define D_USER "Felhasználó"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV index"

View File

@ -28,7 +28,7 @@
* Use online command StateText to translate ON, OFF, HOLD and TOGGLE.
* Use online command Prefix to translate cmnd, stat and tele.
*
* Updated until v9.4.0.1 - Last update 05.05.2022
* Updated until v9.4.0.1 - Last update 15.05.2022
\*********************************************************************/
#define LANGUAGE_MODULE_NAME // Enable to display "Module Generic" (ie Spanish), Disable to display "Generic Module" (ie English)
@ -140,6 +140,7 @@
#define D_PASSWORD "Password"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partizione" // As in flash and firmware partition
#define D_PORT "Porta"
#define D_POWER_FACTOR "Fattore di potenza"
#define D_POWERUSAGE "Potenza"
@ -177,12 +178,13 @@
#define D_TOGGLE "ON/OFF"
#define D_TOPIC "Topic"
#define D_TOTAL_USAGE "Uso totale"
#define D_TRANSMIT "Trasmesso"
#define D_TRANSMIT "Trasmessi"
#define D_TRUE "Vero"
#define D_TVOC "TVOC"
#define D_UPGRADE "aggiornamento"
#define D_UPLOAD "Caricamento"
#define D_UPTIME "Tempo accensione"
#define D_USED "usati"
#define D_USER "Utente"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Indice UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "비밀번호"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "포트"
#define D_POWER_FACTOR "Power Factor"
#define D_POWERUSAGE "전원"
@ -183,6 +184,7 @@
#define D_UPGRADE "업그레이드"
#define D_UPLOAD "업로드"
#define D_UPTIME "가동시간"
#define D_USED "used"
#define D_USER "User"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV 색인"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Wachtwoord"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Poort"
#define D_POWER_FACTOR "Arbeidsfactor"
#define D_POWERUSAGE "Vermogen"
@ -183,6 +184,7 @@
#define D_UPGRADE "opwaarderen"
#define D_UPLOAD "Verzenden"
#define D_UPTIME "Bedrijfstijd"
#define D_USED "used"
#define D_USER "Gebruiker"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV-index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Hasło"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Cosinus fi"
#define D_POWERUSAGE "Moc"
@ -183,6 +184,7 @@
#define D_UPGRADE "aktualizacji"
#define D_UPLOAD "Wgraj"
#define D_UPTIME "Czas pracy"
#define D_USED "used"
#define D_USER "Użytkownik"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Indeks UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Senha"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Porta"
#define D_POWER_FACTOR "Fator de potência"
#define D_POWERUSAGE "Potência"
@ -183,6 +184,7 @@
#define D_UPGRADE "atualização"
#define D_UPLOAD "Enviar"
#define D_UPTIME "Tempo de atividade"
#define D_USED "used"
#define D_USER "Usuário"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Índice UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Palavra Chave"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Porta"
#define D_POWER_FACTOR "Factor de Potência"
#define D_POWERUSAGE "Potência"
@ -183,6 +184,7 @@
#define D_UPGRADE "Atualizar"
#define D_UPLOAD "Enviar"
#define D_UPTIME "Tempo de Atividade"
#define D_USED "used"
#define D_USER "Utilizador"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Indíce UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Parolă"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Factor de Putere"
#define D_POWERUSAGE "Putere"
@ -183,6 +184,7 @@
#define D_UPGRADE "actualizare"
#define D_UPLOAD "Încărcăre"
#define D_UPTIME "Folosință"
#define D_USED "used"
#define D_USER "Utilizator"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Index UV"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Пароль"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Порт"
#define D_POWER_FACTOR "Коэффициент мощности"
#define D_POWERUSAGE "Мощность"
@ -183,6 +184,7 @@
#define D_UPGRADE "обновление"
#define D_UPLOAD "Загрузить"
#define D_UPTIME "Uptime"
#define D_USED "used"
#define D_USER "Пользователь"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Heslo"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Účinník"
#define D_POWERUSAGE "Príkon"
@ -183,6 +184,7 @@
#define D_UPGRADE "aktualizáciu"
#define D_UPLOAD "Nahrávanie..."
#define D_UPTIME "Uptime"
#define D_USED "used"
#define D_USER "Používateľ"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Lösenord"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Spänningsfaktor"
#define D_POWERUSAGE "Spänning"
@ -183,6 +184,7 @@
#define D_UPGRADE "uppgradera"
#define D_UPLOAD "Ladda upp"
#define D_UPTIME "Upptid"
#define D_USED "used"
#define D_USER "Användare"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Şifre"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Port"
#define D_POWER_FACTOR "Güç Faktörü"
#define D_POWERUSAGE "Güç"
@ -183,6 +184,7 @@
#define D_UPGRADE "yükseltme"
#define D_UPLOAD "Yükleme"
#define D_UPTIME "Açık Kalma Süresi"
#define D_USED "used"
#define D_USER "Kullanıcı"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Indeksi"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Гасло"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Порт"
#define D_POWER_FACTOR "Коефіцієнт потужності"
#define D_POWERUSAGE "Потужність"
@ -183,6 +184,7 @@
#define D_UPGRADE "оновлення"
#define D_UPLOAD "Завантажити"
#define D_UPTIME "Час роботи"
#define D_USED "used"
#define D_USER "Користувач"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "Індекс УФ"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "Mật khẩu"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "Cổng"
#define D_POWER_FACTOR "Hệ số công suất"
#define D_POWERUSAGE "Công suất"
@ -183,6 +184,7 @@
#define D_UPGRADE "nâng cấp"
#define D_UPLOAD "Tải lên"
#define D_UPTIME "Thời gian chạy"
#define D_USED "used"
#define D_USER "Người dùng"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "UV Index"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "密码"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "端口"
#define D_POWER_FACTOR "功率因数"
#define D_POWERUSAGE "功率"
@ -183,6 +184,7 @@
#define D_UPGRADE "升级"
#define D_UPLOAD "上传"
#define D_UPTIME "运行时间"
#define D_USED "used"
#define D_USER "用户名"
#define D_UTC_TIME "UTC"
#define D_UV_INDEX "紫外线指数"

View File

@ -140,6 +140,7 @@
#define D_PASSWORD "密碼"
#define D_PH "pH"
#define D_MQ "MQ"
#define D_PARTITION "Partition" // As in flash and firmware partition
#define D_PORT "通訊埠"
#define D_POWER_FACTOR "功率因數"
#define D_POWERUSAGE "用電量"
@ -183,6 +184,7 @@
#define D_UPGRADE "升級"
#define D_UPLOAD "上傳"
#define D_UPTIME "啟動時間"
#define D_USED "used"
#define D_USER "使用者名稱"
#define D_UTC_TIME "世界協調時間(UTC)"
#define D_UV_INDEX "紫外線指數"

View File

@ -837,7 +837,15 @@ void CmndUpgrade(void)
TasmotaGlobal.ota_state_flag = 3;
char stemp1[TOPSZ];
Response_P(PSTR("{\"%s\":\"" D_JSON_VERSION " %s " D_JSON_FROM " %s\"}"), XdrvMailbox.command, TasmotaGlobal.version, GetOtaUrl(stemp1, sizeof(stemp1)));
} else {
}
#if defined(ESP32) && defined(USE_WEBCLIENT_HTTPS)
else if (EspSingleOtaPartition() && !EspRunningFactoryPartition() && (1 == XdrvMailbox.data_len) && (2 == XdrvMailbox.payload)) {
TasmotaGlobal.ota_factory = true;
TasmotaGlobal.ota_state_flag = 3;
ResponseCmndChar(PSTR("Safeboot"));
}
#endif // ESP32 and WEBCLIENT_HTTPS
else {
Response_P(PSTR("{\"%s\":\"" D_JSON_ONE_OR_GT "\"}"), XdrvMailbox.command, TasmotaGlobal.version);
}
}
@ -883,7 +891,7 @@ void CmndRestart(void)
break;
#ifdef ESP32
case 3:
if (EspPrepSwitchToOtherPartition()) {
if (EspPrepSwitchPartition(2)) { // Toggle partition between safeboot and production
TasmotaGlobal.restart_flag = 2;
ResponseCmndChar(PSTR("Switching"));
}

View File

@ -269,6 +269,17 @@ void QPCWrite(const void *pSettings, unsigned nSettingsLen) {
NvmSave("qpc", "pcreg", pSettings, nSettingsLen);
}
bool OtaFactoryRead(void) {
uint32_t pOtaLoader;
NvmLoad("otal", "otal", &pOtaLoader, sizeof(pOtaLoader));
return pOtaLoader;
}
void OtaFactoryWrite(bool value) {
uint32_t pOtaLoader = value;
NvmSave("otal", "otal", &pOtaLoader, sizeof(pOtaLoader));
}
void NvsInfo(void) {
nvs_stats_t nvs_stats;
nvs_get_stats(NULL, &nvs_stats);
@ -308,6 +319,21 @@ extern "C" {
#include "rom/spi_flash.h"
#endif
uint32_t EspProgramSize(const char *label) {
const esp_partition_t *part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, label);
if (!part) {
return 0;
}
const esp_partition_pos_t part_pos = {
.offset = part->address,
.size = part->size,
};
esp_image_metadata_t data;
data.start_addr = part_pos.offset;
esp_image_verify(ESP_IMAGE_VERIFY, &part_pos, &data);
return data.image_len;
}
bool EspSingleOtaPartition(void) {
return (1 == esp_ota_get_app_partition_count());
}
@ -328,15 +354,33 @@ void EspPrepRestartToSafeBoot(void) {
}
}
bool EspPrepSwitchToOtherPartition(void) {
bool EspPrepSwitchPartition(uint32_t state) {
bool valid = EspSingleOtaPartition();
if (valid) {
bool running_factory = EspRunningFactoryPartition();
if (!running_factory) {
EspPrepRestartToSafeBoot();
} else {
const esp_partition_t* partition = esp_ota_get_next_update_partition(nullptr);
esp_ota_set_boot_partition(partition);
switch (state) {
case 0: // Off = safeboot
if (!running_factory) {
EspPrepRestartToSafeBoot();
} else {
valid = false;
}
break;
case 1: // On = ota0
if (running_factory) {
const esp_partition_t* partition = esp_ota_get_next_update_partition(nullptr);
esp_ota_set_boot_partition(partition);
} else {
valid = false;
}
break;
case 2: // Toggle
if (!running_factory) {
EspPrepRestartToSafeBoot();
} else {
const esp_partition_t* partition = esp_ota_get_next_update_partition(nullptr);
esp_ota_set_boot_partition(partition);
}
}
}
return valid;

View File

@ -1175,6 +1175,9 @@ void Every250mSeconds(void)
if (TasmotaGlobal.ota_state_flag && CommandsReady()) {
TasmotaGlobal.ota_state_flag--;
if (2 == TasmotaGlobal.ota_state_flag) {
#ifdef CONFIG_IDF_TARGET_ESP32C3
OtaFactoryWrite(false);
#endif
RtcSettings.ota_loader = 0; // Try requested image first
ota_retry_counter = OTA_ATTEMPTS;
SettingsSave(1); // Free flash for OTA update
@ -1245,7 +1248,25 @@ void Every250mSeconds(void)
#ifdef ESP32
#ifndef FIRMWARE_MINIMAL
#ifdef USE_WEBCLIENT_HTTPS
if (TasmotaGlobal.ota_factory) {
char *bch = strrchr(full_ota_url, '/'); // Only consider filename after last backslash prevent change of urls having "-" in it
if (bch == nullptr) { bch = full_ota_url; } // No path found so use filename only
char *ech = strchr(bch, '.'); // Find file type in filename (none, .ino.bin, .ino.bin.gz, .bin, .bin.gz or .gz)
if (ech == nullptr) { ech = full_ota_url + strlen(full_ota_url); } // Point to '/0' at end of full_ota_url becoming an empty string
char ota_url_type[strlen(ech) +1];
strncpy(ota_url_type, ech, sizeof(ota_url_type)); // Either empty, .ino.bin, .ino.bin.gz, .bin, .bin.gz or .gz
char *pch = strrchr(bch, '-'); // Find last dash (-) and ignore remainder - handles tasmota-DE
if (pch == nullptr) { pch = ech; } // No dash so ignore filetype
*pch = '\0'; // full_ota_url = http://domus1:80/api/arduino/tasmota
snprintf_P(full_ota_url, sizeof(full_ota_url), PSTR("%s-safeboot%s"), full_ota_url, ota_url_type); // Safeboot filename must be filename-safeboot
} else
#endif // USE_WEBCLIENT_HTTPS
if (EspSingleOtaPartition()) {
#ifdef CONFIG_IDF_TARGET_ESP32C3
OtaFactoryWrite(true);
#endif
RtcSettings.ota_loader = 1; // Try safeboot image next
SettingsSaveAll();
AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING));
@ -1265,6 +1286,7 @@ void Every250mSeconds(void)
ota_result = -999;
} else {
httpUpdateLight.rebootOnUpdate(false);
httpUpdateLight.setFactory(TasmotaGlobal.ota_factory);
ota_result = (HTTP_UPDATE_FAILED != httpUpdateLight.update(OTAclient, version));
}
#else // standard OTA over HTTP
@ -1413,6 +1435,12 @@ void Every250mSeconds(void)
case 3: // Every x.75 second
if (!TasmotaGlobal.global_state.network_down) {
#ifdef FIRMWARE_MINIMAL
#ifdef CONFIG_IDF_TARGET_ESP32C3
if (OtaFactoryRead()) {
OtaFactoryWrite(false);
TasmotaGlobal.ota_state_flag = 3;
}
#endif
if (1 == RtcSettings.ota_loader) {
RtcSettings.ota_loader = 0;
TasmotaGlobal.ota_state_flag = 3;

View File

@ -186,6 +186,7 @@ struct TasmotaGlobal_t {
bool i2c_enabled; // I2C configured
#ifdef ESP32
bool i2c_enabled_2; // I2C configured, second controller on ESP32, Wire1
bool ota_factory; // Select safeboot binary
#endif
bool ntp_force_sync; // Force NTP sync
bool skip_light_fade; // Temporarily skip light fading

View File

@ -104,6 +104,12 @@ String EthernetMacAddress(void);
#if CONFIG_IDF_TARGET_ESP32
#ifdef CORE32SOLO1
#ifdef USE_ETHERNET
#undef USE_ETHERNET // ESP32-Solo1 does not support ethernet
#endif
#endif // CORE32SOLO1
#else // Disable features not present in other ESP32 like ESP32C3, ESP32S2, ESP32S3 etc.
#ifdef USE_ETHERNET
#undef USE_ETHERNET // All non-ESP32 do not support ethernet

View File

@ -20,6 +20,6 @@
#ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x0B010002; // 11.1.0.2
const uint32_t VERSION = 0x0B010003; // 11.1.0.3
#endif // _TASMOTA_VERSION_H_

View File

@ -353,18 +353,22 @@ const char HTTP_FORM_UPG[] PROGMEM =
"</fieldset><br><br>"
"<fieldset><legend><b>&nbsp;" D_UPGRADE_BY_FILE_UPLOAD "&nbsp;</b></legend>";
const char HTTP_FORM_RST_UPG[] PROGMEM =
"<form method='post' action='u2' enctype='multipart/form-data'>"
"<form method='post' action='u2?fsz=' enctype='multipart/form-data'>"
"<br><input type='file' name='u2'><br>"
"<br><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.submit();'>" D_START " %s</button></form>"
"<br><button type='submit' "
"onclick='eb(\"f1\").style.display=\"none\";eb(\"f2\").style.display=\"block\";this.form.action+=this.form[\"u2\"].files[0].size;this.form.submit();'"
">" D_START " %s</button></form>"
"</fieldset>"
"</div>"
"<div id='f2' style='display:none;text-align:center;'><b>" D_UPLOAD_STARTED "...</b></div>";
// upload via factory partition
const char HTTP_FORM_RST_UPG_FCT[] PROGMEM =
"<form method='post' action='u2' enctype='multipart/form-data'>"
"<form method='post' action='u2?fsz=' enctype='multipart/form-data'>"
"<br><input type='file' name='u2'><br>"
"<br><button type='submit' onclick='eb(\"f1\").style.display=\"none\";eb(\"f3\").style.display=\"block\";return upl(this);'>" D_START " %s</button></form>"
"<br><button type='submit' "
"onclick='eb(\"f1\").style.display=\"none\";eb(\"f3\").style.display=\"block\";this.form.action+=this.form[\"u2\"].files[0].size;return upl(this);'"
">" D_START " %s</button></form>"
"</fieldset>"
"</div>"
"<div id='f3' style='display:none;text-align:center;'><b>" D_UPLOAD_FACTORY "...</b></div>"
@ -436,6 +440,7 @@ ESP8266WebServer *Webserver;
struct WEB {
String chunk_buffer = ""; // Could be max 2 * CHUNKED_BUFFER_SIZE
uint32_t upload_size = 0;
uint16_t upload_error = 0;
uint8_t state = HTTP_OFF;
uint8_t upload_file_type;
@ -556,8 +561,10 @@ const WebServerDispatch_t WebServerDispatch[] PROGMEM = {
{ "dl", HTTP_ANY, HandleBackupConfiguration },
{ "rs", HTTP_ANY, HandleRestoreConfiguration },
{ "rt", HTTP_ANY, HandleResetConfiguration },
{ "in", HTTP_ANY, HandleInformation },
#endif // Not FIRMWARE_MINIMAL
#ifndef FIRMWARE_MINIMAL_ONLY
{ "in", HTTP_ANY, HandleInformation },
#endif // Not FIRMWARE_MINIMAL_ONLY
};
void WebServer_on(const char * prefix, void (*func)(void), uint8_t method = HTTP_ANY) {
@ -995,12 +1002,16 @@ void WSContentStop(void) {
/*********************************************************************************************/
void WebRestart(uint32_t type)
{
void WebRestart(uint32_t type) {
// type 0 = restart
// type 1 = restart after config change
// type 2 = Checking WiFi Connection - no restart, only refresh page.
// type 3 = restart after WiFi Connection Test Successful
// type 4 = type 0 without auto switch to production
bool prep_switch_partition = false;
if (0 == type) { prep_switch_partition = true; }
if (4 == type) { type = 0; }
bool reset_only = (HTTP_MANAGER_RESET_ONLY == Web.state);
WSContentStart_P((type) ? PSTR(D_SAVE_CONFIGURATION) : PSTR(D_RESTART), !reset_only);
@ -1033,7 +1044,7 @@ void WebRestart(uint32_t type)
#endif
}
}
if (type<2) {
if (type < 2) {
WSContentSend_P(HTTP_MSG_RSTRT);
if (HTTP_MANAGER == Web.state || reset_only) {
Web.state = HTTP_ADMIN;
@ -1046,6 +1057,9 @@ void WebRestart(uint32_t type)
if (!(2 == type)) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_RESTART));
ShowWebSource(SRC_WEBGUI);
#ifdef ESP32
if (prep_switch_partition) { EspPrepSwitchPartition(1); } // Switch to production partition if on safeboot
#endif // ESP32
TasmotaGlobal.restart_flag = 2;
}
}
@ -1286,7 +1300,14 @@ void HandleRoot(void)
if (HTTP_ADMIN == Web.state) {
#ifdef FIRMWARE_MINIMAL
#ifdef ESP32
#ifndef FIRMWARE_MINIMAL_ONLY
WSContentSpaceButton(BUTTON_INFORMATION);
WSContentButton(BUTTON_FIRMWARE_UPGRADE);
#endif // FIRMWARE_MINIMAL_ONLY
#else // ESP8266
WSContentSpaceButton(BUTTON_FIRMWARE_UPGRADE);
#endif // ESP32
WSContentButton(BUTTON_CONSOLE);
#else
WSContentSpaceButton(BUTTON_CONFIGURATION);
@ -1831,25 +1852,6 @@ void ModuleSaveSettings(void) {
/*-------------------------------------------------------------------------------------------*/
const char kUnescapeCode[] = "&><\"\'\\";
const char kEscapeCode[] PROGMEM = "&amp;|&gt;|&lt;|&quot;|&apos;|&#92;";
String HtmlEscape(const String unescaped) {
char escaped[10];
size_t ulen = unescaped.length();
String result = "";
for (size_t i = 0; i < ulen; i++) {
char c = unescaped[i];
char *p = strchr(kUnescapeCode, c);
if (p != nullptr) {
result += GetTextIndexed(escaped, sizeof(escaped), p - kUnescapeCode, kEscapeCode);
} else {
result += c;
}
}
return result;
}
void HandleWifiConfiguration(void) {
char tmp[TOPSZ]; // Max length is currently 150
@ -2325,8 +2327,31 @@ void HandleRestoreConfiguration(void)
Web.upload_file_type = UPL_SETTINGS;
}
#endif // Not FIRMWARE_MINIMAL
/*-------------------------------------------------------------------------------------------*/
#ifndef FIRMWARE_MINIMAL_ONLY
const char kUnescapeCode[] = "&><\"\'\\";
const char kEscapeCode[] PROGMEM = "&amp;|&gt;|&lt;|&quot;|&apos;|&#92;";
String HtmlEscape(const String unescaped) {
char escaped[10];
size_t ulen = unescaped.length();
String result = "";
for (size_t i = 0; i < ulen; i++) {
char c = unescaped[i];
char *p = strchr(kUnescapeCode, c);
if (p != nullptr) {
result += GetTextIndexed(escaped, sizeof(escaped), p - kUnescapeCode, kEscapeCode);
} else {
result += c;
}
}
return result;
}
void HandleInformation(void)
{
if (!HttpCheckPriviledgedAccess()) { return; }
@ -2453,18 +2478,52 @@ void HandleInformation(void)
WSContentSend_P(PSTR("}1}2&nbsp;")); // Empty line
WSContentSend_P(PSTR("}1" D_ESP_CHIP_ID "}2%d (%s)"), ESP_getChipId(), GetDeviceHardwareRevision().c_str());
WSContentSend_P(PSTR("}1" D_FLASH_CHIP_ID "}20x%06X"), ESP_getFlashChipId());
WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%d kB"), ESP_getFlashChipRealSize() / 1024);
WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%d kB"), ESP.getFlashChipSize() / 1024);
WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%d kB"), ESP_getSketchSize() / 1024);
WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%d kB"), ESP_getFreeSketchSpace() / 1024);
WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%d KB"), ESP_getFlashChipRealSize() / 1024);
WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%d KB"), ESP.getFlashChipSize() / 1024);
WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%d KB"), ESP_getSketchSize() / 1024);
WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%d KB"), ESP_getFreeSketchSpace() / 1024);
#ifdef ESP32
WSContentSend_PD(PSTR("}1" D_FREE_MEMORY "}2%1_f kB (" D_FRAGMENTATION " %d%%)"), &freemem, ESP_getHeapFragmentation());
WSContentSend_PD(PSTR("}1" D_FREE_MEMORY "}2%1_f KB (" D_FRAGMENTATION " %d%%)"), &freemem, ESP_getHeapFragmentation());
if (UsePSRAM()) {
WSContentSend_P(PSTR("}1" D_PSR_MAX_MEMORY "}2%d kB"), ESP.getPsramSize() / 1024);
WSContentSend_P(PSTR("}1" D_PSR_FREE_MEMORY "}2%d kB"), ESP.getFreePsram() / 1024);
WSContentSend_P(PSTR("}1" D_PSR_MAX_MEMORY "}2%d KB"), ESP.getPsramSize() / 1024);
WSContentSend_P(PSTR("}1" D_PSR_FREE_MEMORY "}2%d KB"), ESP.getFreePsram() / 1024);
}
WSContentSend_P(PSTR("}1}2&nbsp;")); // Empty line
uint32_t cur_part = ESP_PARTITION_SUBTYPE_APP_FACTORY; // 0
const esp_partition_t *running_ota = esp_ota_get_running_partition();
if (running_ota) { cur_part = running_ota->subtype; } // 16 - 32
esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
for (; it != NULL; it = esp_partition_next(it)) {
const esp_partition_t *part = esp_partition_get(it);
// AddLog(LOG_LEVEL_DEBUG, PSTR("PRT: Type %d, Subtype %d, Name %s, Size %d"), part->type, part->subtype, part->label, part->size);
uint32_t part_size = part->size / 1024;
if (ESP_PARTITION_TYPE_APP == part->type) {
uint32_t prog_size = 0; // No active ota partition
if (part->subtype == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
prog_size = EspProgramSize(part->label); // safeboot partition (slow response)
}
else if ((part->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MIN) && (part->subtype <= ESP_PARTITION_SUBTYPE_APP_OTA_MAX)) {
if (cur_part == part->subtype) {
prog_size = ESP_getSketchSize(); // Active running ota partition (fast response)
}
else if (cur_part == ESP_PARTITION_SUBTYPE_APP_FACTORY) {
prog_size = EspProgramSize(part->label); // One app partition when safeboot partitions (slow response)
}
}
char running[2] = { 0 };
if (part->subtype == cur_part) { running[0] = '*'; }
uint32_t part_used = ((prog_size / 1024) * 100) / part_size;
WSContentSend_PD(PSTR("}1" D_PARTITION " %s%s}2%d KB (" D_USED " %d%%)"), part->label, running, part_size, part_used);
}
if ((ESP_PARTITION_TYPE_DATA == part->type) && (ESP_PARTITION_SUBTYPE_DATA_SPIFFS == part->subtype)) {
WSContentSend_PD(PSTR("}1" D_PARTITION " fs}2%d KB"), part_size);
}
}
esp_partition_iterator_release(it);
#else // ESP32
WSContentSend_PD(PSTR("}1" D_FREE_MEMORY "}2%1_f kB"), &freemem);
WSContentSend_PD(PSTR("}1" D_FREE_MEMORY "}2%1_f KB"), &freemem);
#endif // ESP32
WSContentSend_P(PSTR("</td></tr></table>"));
@ -2477,7 +2536,7 @@ void HandleInformation(void)
WSContentSpaceButton(BUTTON_MAIN);
WSContentStop();
}
#endif // Not FIRMWARE_MINIMAL
#endif // Not FIRMWARE_MINIMAL_ONLY
/*-------------------------------------------------------------------------------------------*/
@ -2775,11 +2834,15 @@ void HandleUploadLoop(void) {
return;
}
if (0xE9 == upload.buf[0]) {
uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
#ifdef ESP8266
uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
if (bin_flash_size > ESP.getFlashChipRealSize()) {
#else
if (bin_flash_size > ESP.getFlashChipSize()) { // TODO revisit this test
char tmp[16];
WebGetArg("fsz", tmp, sizeof(tmp)); // filesize
uint32_t upload_size = (!strlen(tmp)) ? 0 : atoi(tmp);
AddLog(LOG_LEVEL_DEBUG, D_LOG_UPLOAD "freespace=%i filesize=%i", ESP.getFreeSketchSpace(), upload_size);
if (upload_size > ESP.getFreeSketchSpace()) { // TODO revisit this test
#endif
Web.upload_error = 4; // Program flash size is larger than real flash size
return;
@ -2826,7 +2889,7 @@ void HandleUploadLoop(void) {
return;
}
if (upload.totalSize && !(upload.totalSize % 102400)) {
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d kB"), upload.totalSize / 1024);
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d KB"), upload.totalSize / 1024);
}
}
@ -2981,7 +3044,7 @@ void HandleSwitchBootPartition(void)
AddLog(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_RESTART));
EspRestart();
} else {
WebRestart(0);
WebRestart(4);
}
} else {
if (api_mode) {

View File

@ -209,6 +209,18 @@ void TasDiscoverMessage(void) {
}
ResponseAppend_P(PSTR("]," // Shutter Options (end)
"\"sht\":[")); // Shutter Tilt (start)
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
#ifdef USE_SHUTTER
ResponseAppend_P(PSTR("%s[%d,%d,%d]"), (i > 0 ? "," : ""),
Settings->shutter_tilt_config[0][i],
Settings->shutter_tilt_config[1][i],
Settings->shutter_tilt_config[2][i]);
#else
ResponseAppend_P(PSTR("%s[0,0,0]"), (i > 0 ? "," : ""));
#endif // USE_SHUTTER
}
ResponseAppend_P(PSTR("]," // Shutter Tilt (end)
"\"ver\":1}")); // Discovery version
}

View File

@ -376,6 +376,18 @@ void HassDiscoverMessage(void) {
}
ResponseAppend_P(PSTR("]," // Shutter Options (end)
"\"sht\":[")); // Shutter Tilt (start)
for (uint32_t i = 0; i < MAX_SHUTTERS; i++) {
#ifdef USE_SHUTTER
ResponseAppend_P(PSTR("%s[%d,%d,%d]"), (i > 0 ? "," : ""),
Settings->shutter_tilt_config[0][i],
Settings->shutter_tilt_config[1][i],
Settings->shutter_tilt_config[2][i]);
#else
ResponseAppend_P(PSTR("%s[0,0,0]"), (i > 0 ? "," : ""));
#endif // USE_SHUTTER
}
ResponseAppend_P(PSTR("]," // Shutter Tilt (end)
"\"ver\":1}")); // Discovery version
}

View File

@ -1489,8 +1489,10 @@ void ZCLFrame::parseReadAttributes(Z_attribute_list& attr_list) {
attr_list.addAttributePMEM(PSTR("Read")).setStrRaw(attr_numbers.toString().c_str());
attr_list.addAttributePMEM(PSTR("ReadNames")).setStrRaw(attr_names.toString(true).c_str());
// call auto-responder
autoResponder(read_attr_ids, len/2);
// call auto-responder only if src address if different from ourselves and it was a broadcast
if (_srcaddr != localShortAddr || !_wasbroadcast) {
autoResponder(read_attr_ids, len/2);
}
}
// ZCL_CONFIGURE_REPORTING_RESPONSE

View File

@ -201,8 +201,8 @@ void zigbeeZCLSendCmd(class ZCLMessage &zcl) {
zcl.transacSet = true;
}
AddLog(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%_B\""),
zcl.shortaddr, zcl.groupaddr, zcl.endpoint, zcl.cluster, zcl.cmd, &zcl.buf);
AddLog(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend %s: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%_B\""),
zcl.validShortaddr() ? "device":"group", zcl.validShortaddr() ? zcl.shortaddr : zcl.groupaddr, zcl.endpoint, zcl.cluster, zcl.cmd, &zcl.buf);
ZigbeeZCLSend_Raw(zcl);

View File

@ -1,7 +1,7 @@
/*
xdrv_27_Shutter[i].ino - Shutter/Blind support for Tasmota
Copyright (C) 2022 Stefan Bode
Copyright (C) 2022 Stefan Bode
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -126,6 +126,7 @@ struct SHUTTERGLOBAL {
#define SHT_DIV_ROUND(__A, __B) (((__A) + (__B)/2) / (__B))
void ShutterLogPos(uint32_t i)
{
char stemp2[10];
@ -175,7 +176,7 @@ void ShutterRtc50mS(void)
if (Shutter[i].accelerator) {
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Accelerator i=%d -> %d"),i, Shutter[i].accelerator);
ShutterUpdateVelocity(i);
digitalWrite(Pin(GPIO_PWM1, i), LOW);
digitalWrite(Pin(GPIO_PWM1, i), LOW);
#ifdef ESP8266
// Convert frequency into clock cycles
uint32_t cc = microsecondsToClockCycles(1000000UL) / Shutter[i].pwm_velocity;
@ -1242,8 +1243,8 @@ void CmndShutterPosition(void)
ShutterReportPosition(true, index);
}
XdrvMailbox.index = index +1; // Fix random index for ShutterClose
if (XdrvMailbox.command)
ResponseCmndIdxNumber((Settings->shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent);
strcpy( XdrvMailbox.command , D_CMND_SHUTTER_POSITION);
ResponseCmndIdxNumber((Settings->shutter_options[index] & 1) ? 100 - target_pos_percent : target_pos_percent);
} else {
ShutterReportPosition(true, MAX_SHUTTERS);
if (XdrvMailbox.command)
@ -1632,7 +1633,6 @@ void CmndShutterSetTilt(void)
}
XdrvMailbox.data[0] = '\0';
AddLog(LOG_LEVEL_INFO, PSTR("SHT: TiltTarget %d, payload %d"), Shutter[XdrvMailbox.index -1].tilt_target_pos,XdrvMailbox.payload);
ResponseCmndNumber(Shutter[XdrvMailbox.index -1].tilt_target_pos);
Shutter[XdrvMailbox.index -1].tiltmoving = 1;
CmndShutterPosition();
}
@ -1649,27 +1649,26 @@ void CmndShutterTiltConfig(void)
for (char *str = strtok_r(data_copy, " ", &str_ptr); str && i < 6; str = strtok_r(nullptr, " ", &str_ptr), i++) {
Shutter[XdrvMailbox.index -1].tilt_config[i] = Settings->shutter_tilt_config[i][XdrvMailbox.index -1] = atoi(str);
}
// avoid negative runtime
Settings->shutter_tilt_config[2][XdrvMailbox.index -1] = Shutter[XdrvMailbox.index -1].tilt_config[2] = Shutter[XdrvMailbox.index -1].tilt_config[2] >= 0 ? Shutter[XdrvMailbox.index -1].tilt_config[2] : 127;
ShutterInit();
ResponseCmndIdxChar(XdrvMailbox.data);
} else {
char setting_chr[30] = "0";
snprintf_P(setting_chr, sizeof(setting_chr), PSTR("SHT:%d %d %d %d %d %d"), XdrvMailbox.index -1,Shutter[XdrvMailbox.index -1].tilt_config[0], Shutter[XdrvMailbox.index -1].tilt_config[1],Shutter[XdrvMailbox.index -1].tilt_config[2],Shutter[XdrvMailbox.index -1].tilt_config[3],Shutter[XdrvMailbox.index -1].tilt_config[4]);
ResponseCmndIdxChar(setting_chr);
}
AddLog(LOG_LEVEL_INFO, PSTR("SHT: TiltConfig %d, min: %d, max %d, runtime %d, close_pos: %d, open_pos: %d"), XdrvMailbox.index ,Shutter[XdrvMailbox.index -1].tilt_config[0], Shutter[XdrvMailbox.index -1].tilt_config[1],Shutter[XdrvMailbox.index -1].tilt_config[2],Shutter[XdrvMailbox.index -1].tilt_config[3],Shutter[XdrvMailbox.index -1].tilt_config[4]);
char setting_chr[30] = "0";
snprintf_P(setting_chr, sizeof(setting_chr), PSTR("%d %d %d %d %d"), XdrvMailbox.index -1,Shutter[XdrvMailbox.index -1].tilt_config[0], Shutter[XdrvMailbox.index -1].tilt_config[1],Shutter[XdrvMailbox.index -1].tilt_config[2],Shutter[XdrvMailbox.index -1].tilt_config[3],Shutter[XdrvMailbox.index -1].tilt_config[4]);
ResponseCmndIdxChar(setting_chr);
AddLog(LOG_LEVEL_INFO, PSTR("SHT: TiltConfig %d, min: %d, max %d, runtime %d, close_pos: %d, open_pos: %d"), XdrvMailbox.index ,Shutter[XdrvMailbox.index -1].tilt_config[0], Shutter[XdrvMailbox.index -1].tilt_config[1],Shutter[XdrvMailbox.index -1].tilt_config[2],Shutter[XdrvMailbox.index -1].tilt_config[3],Shutter[XdrvMailbox.index -1].tilt_config[4]);
}
}
void CmndShutterTiltIncDec(void)
{
//AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("SHT: Change in: payload %s (%d), payload %d, idx %d, src %d"), XdrvMailbox.data , XdrvMailbox.data_len, XdrvMailbox.payload , XdrvMailbox.index, TasmotaGlobal.last_source );
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present)) {
if (XdrvMailbox.data_len > 0) {
XdrvMailbox.payload = Shutter[XdrvMailbox.index -1].tilt_target_pos+XdrvMailbox.payload;
CmndShutterSetTilt();
}
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= TasmotaGlobal.shutters_present) && XdrvMailbox.data_len > 0) {
XdrvMailbox.payload = Shutter[XdrvMailbox.index -1].tilt_target_pos+XdrvMailbox.payload;
CmndShutterSetTilt();
} else {
ResponseCmndIdxNumber(XdrvMailbox.payload);
}
ResponseCmndNumber(Shutter[XdrvMailbox.index -1].tilt_target_pos);
}
/*********************************************************************************************\

View File

@ -45,13 +45,13 @@ size_t FlashWriteSubSector(uint32_t address_start, const uint8_t *data, size_t s
memcpy(buffer, data + current_offset, SPI_FLASH_SEC_SIZE);
} else {
ret = spi_flash_read(page_addr, buffer, SPI_FLASH_SEC_SIZE);
if (ret) { AddLog(LOG_LEVEL_INFO, "BRY: could not read flash %p (0x%X)", page_addr, SPI_FLASH_SEC_SIZE); return 0; }
if (ret) { AddLog(LOG_LEVEL_INFO, "BRY: could not read flash %p (0x%X) ret=%i", page_addr, SPI_FLASH_SEC_SIZE, ret); return 0; }
memcpy(buffer + addr_in_page, data + current_offset, size_in_page);
}
ret = spi_flash_erase_sector(page_addr / SPI_FLASH_SEC_SIZE);
if (ret) { AddLog(LOG_LEVEL_INFO, "BRY: could not erase flash sector 0x%X", page_addr / SPI_FLASH_SEC_SIZE); return 0; }
if (ret) { AddLog(LOG_LEVEL_INFO, "BRY: could not erase flash sector 0x%X ret=%i", page_addr / SPI_FLASH_SEC_SIZE, ret); return 0; }
spi_flash_write(page_addr, buffer, SPI_FLASH_SEC_SIZE);
if (ret) { AddLog(LOG_LEVEL_INFO, "BRY: could not write flash %p (0x%X)", page_addr, SPI_FLASH_SEC_SIZE); return 0; }
if (ret) { AddLog(LOG_LEVEL_INFO, "BRY: could not write flash %p (0x%X) ret=%i", page_addr, SPI_FLASH_SEC_SIZE, ret); return 0; }
addr += size_in_page;
current_offset += size_in_page;
@ -63,9 +63,9 @@ size_t FlashWriteSubSector(uint32_t address_start, const uint8_t *data, size_t s
/*********************************************************************************************\
* Native functions mapped to Berry functions
*
*
* import flash
*
*
\*********************************************************************************************/
extern "C" {
// Berry: `flash.read(address:int[, length:int]) -> bytes()`
@ -74,7 +74,7 @@ extern "C" {
int32_t p_flash_read(struct bvm *vm);
int32_t p_flash_read(struct bvm *vm) {
int32_t argc = be_top(vm); // Get the number of arguments
if (argc >= 1 && be_isint(vm, 1) &&
if (argc >= 1 && be_isint(vm, 1) &&
(argc < 2 || be_isint(vm, 2)) ) { // optional second argument must be int
uint32_t address = be_toint(vm, 1);
uint32_t length = 0x1000;
@ -102,13 +102,24 @@ extern "C" {
if (argc >= 2 && be_isint(vm, 1) && be_isinstance(vm, 2)) {
be_getglobal(vm, "bytes"); /* get the bytes class */ /* TODO eventually replace with be_getbuiltin */
if (be_isderived(vm, 2)) {
bool no_erase = false;
if (argc >= 3 && be_isbool(vm, 3)) {
no_erase = be_tobool(vm, 3);
}
uint32_t address = be_toint(vm, 1);
size_t length = 0;
const void * bytes = be_tobytes(vm, 2, &length);
if (bytes && length > 0) {
size_t ret = FlashWriteSubSector(address, (const uint8_t*)bytes, length);
if (ret == 0) {
be_raise(vm, "internal_error", "Error calling spi_flash_write()");
if (no_erase) {
esp_err_t ret = spi_flash_write(address, (const uint8_t*)bytes, length);
if (ret) {
be_raisef(vm, "internal_error", "Error calling spi_flash_write() ret=%i", ret);
}
} else {
size_t ret = FlashWriteSubSector(address, (const uint8_t*)bytes, length);
if (ret == 0) {
be_raise(vm, "internal_error", "Error calling spi_flash_write()");
}
}
be_return_nil(vm);
// success
@ -146,6 +157,9 @@ extern "C" {
esp_partition_erase_range(otadata_partition, 0, SPI_FLASH_SEC_SIZE * 2);
}
if (force_ota) {
#ifdef CONFIG_IDF_TARGET_ESP32C3
OtaFactoryWrite(true);
#endif
RtcSettings.ota_loader = 1; // force OTA at next reboot
}
}

View File

@ -32,7 +32,7 @@
* Each SPM-4Relay has 4 bistable relays with their own CSE7761 energy monitoring device handled by an ARM processor.
* Green led is controlled by ARM processor indicating SD-Card access.
* ESP32 is used as interface between eWelink and ARM processor in SPM-Main unit communicating over proprietary serial protocol.
* Power on sequence for two SPM-4Relay modules is 00-00-15-10-(0F)-(13)-(13)-(19)-0C-09-04-09-04-0B-0B
* Power on sequence for two SPM-4Relay modules is 00-00-15-10-(0F)-(13)-(13)-(19)-0C-09-04-[25]-09-04-[25]-0B-0B
* Up to 180 days of daily energy are stored on the SD-Card. Previous data is lost.
* Tasmota support is based on Sonoff SPM v1.0.0 ARM firmware.
* Energy history cannot be guaranteed using either SD-Card or internal flash. As a solution Tasmota stores the total energy and yesterday energy just after midnight.
@ -122,7 +122,7 @@
#define XDRV_86 86
#define SSPM_MAX_MODULES 8 // Currently supports up to 8 SPM-4RELAY units for a total of 32 relays restricted by 32-bit power_t size
#define SSPM_SERIAL_BUFFER_SIZE 512 // Needs to accomodate Energy total history for 180 days (408 bytes)
#define SSPM_SERIAL_BUFFER_SIZE 548 // Needs to accomodate firmware upload data blocks (546 bytes)
//#define SSPM_SIMULATE 2 // Simulate additional 4Relay modules based on first detected 4Relay module (debugging purposes only!!)
@ -137,12 +137,17 @@
#define SSPM_FUNC_SET_TIME 12 // 0x0C
#define SSPM_FUNC_IAMHERE 13 // 0x0D
#define SSPM_FUNC_INIT_SCAN 16 // 0x10
#define SSPM_FUNC_UPLOAD_HEADER 20 // 0x14 - Upload header
#define SSPM_FUNC_UNITS 21 // 0x15
#define SSPM_FUNC_GET_ENERGY_TOTAL 22 // 0x16
#define SSPM_FUNC_GET_ENERGY 24 // 0x18
#define SSPM_FUNC_GET_LOG 26 // 0x1A
#define SSPM_FUNC_ENERGY_PERIOD 27 // 0x1B
#define SSPM_FUNC_RESET 28 // 0x1C - Remove device from eWelink and factory reset
#define SSPM_FUNC_ARM_RESTART 30 // 0x1E - Restart ARM
#define SSPM_FUNC_UPLOAD_DATA 31 // 0x1F - Upload incremental data blocks of max 512 bytes to ARM
#define SSPM_FUNC_UPLOAD_DONE 33 // 0x21 - Finish upload
#define SSPM_FUNC_GET_NEW1 37 // 0x25
// From ARM to ESP
#define SSPM_FUNC_ENERGY_RESULT 6 // 0x06
@ -152,20 +157,27 @@
#define SSPM_FUNC_SCAN_DONE 25 // 0x19
// Unknown
#define SSPM_FUNC_01
#define SSPM_FUNC_02
#define SSPM_FUNC_05
#define SSPM_FUNC_14
#define SSPM_FUNC_17
#define SSPM_FUNC_18
#define SSPM_FUNC_20
#define SSPM_FUNC_23
#define SSPM_FUNC_01 1 // 0x01
#define SSPM_FUNC_02 2 // 0x02
#define SSPM_FUNC_05 5 // 0x05
#define SSPM_FUNC_14 14 // 0x0E
#define SSPM_FUNC_17 17 // 0x11
#define SSPM_FUNC_18 18 // 0x12
#define SSPM_FUNC_23 23 // 0x17
#define SSPM_FUNC_29 29 // 0x1D
#define SSPM_FUNC_32 32 // 0x20
#define SSPM_FUNC_34 34 // 0x22
#define SSPM_FUNC_35 35 // 0x23
#define SSPM_FUNC_36 36 // 0x24
#define SSPM_GPIO_ARM_RESET 15
#define SSPM_GPIO_LED_ERROR 33
#define SSPM_MODULE_NAME_SIZE 12
#define SSPM_MAIN_V1_0_0 0x00010000
#define SSPM_MAIN_V1_2_0 0x00010200
/*********************************************************************************************/
#define SSPM_TOTAL_MODULES 32 // Max number of SPM-4RELAY units for a total of 128 relays
@ -176,9 +188,10 @@ enum SspmMachineStates { SPM_NONE, // Do nothing
SPM_WAIT, // Wait 100ms
SPM_RESET, // Toggle ARM reset pin
SPM_POLL_ARM, // Wait for first acknowledge from ARM after reset
SPM_POLL_ARM_SPI, // Wait for first acknowledge from ARM SPI after reset
SPM_POLL_ARM_2, // Wait for second acknowledge from ARM after reset
SPM_POLL_ARM_3, // Wait for second acknowledge from ARM after reset
// Removed to accomodate v1.2.0 too
// SPM_POLL_ARM_SPI, // Wait for first acknowledge from ARM SPI after reset
// SPM_POLL_ARM_2, // Wait for second acknowledge from ARM after reset
// SPM_POLL_ARM_3, // Wait for second acknowledge from ARM after reset
SPM_SEND_FUNC_UNITS, // Get number of units
SPM_START_SCAN, // Start module scan sequence
SPM_WAIT_FOR_SCAN, // Wait for scan sequence to complete
@ -250,6 +263,7 @@ typedef struct {
float overload_max_current;
uint32_t timeout;
uint32_t main_version;
power_t old_power;
power_t power_on_state;
uint16_t last_totals;
@ -785,6 +799,21 @@ void SSPMSendInitScan(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Start relay scan..."));
}
void SSPMSendUploadHeader(void) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 0b 09 09 00 1b e5 a4 c7 00 02 88 74 00 6d df
Marker | | |Cm|Size | |Checksum |UploadSize |Ix|Chksm|
*/
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_UPLOAD_HEADER; // 0x14
SspmBuffer[18] = 0x0B;
SspmBuffer[30] = 0;
SSPMSend(33);
}
void SSPMSendGetEnergyTotal(uint32_t relay) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
@ -894,6 +923,66 @@ void SSPMSendGetEnergyPeriod(uint32_t relay) {
}
void SSPMSendArmRestart(void) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
aa 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1e 00 01 00 01 fe 05
Marker | | |Cm|Size | |Ix|Chksm|
*/
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_ARM_RESTART; // 0x1E
SspmBuffer[18] = 1;
Sspm->command_sequence++;
SspmBuffer[20] = Sspm->command_sequence;
SSPMSend(23);
}
void SSPMSendUpload(void) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 539 540 541 542 543 544 545
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1f 02 0c 00 00 00 00 00 00 02 00 a2 99 c3 22 00 00 01 20 cd 95 01 08 ... 04 48 af f3 01 xx yy
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1f 02 0c 00 00 02 00 00 00 02 00 27 f7 24 87 00 80 01 23 23 70 10 bd ... 21 fa 04 f3 02 xx yy
...
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1f 02 0c 00 02 86 00 00 00 02 00 f8 f5 25 6d f1 61 00 08 02 01 ff 00 ... 44 xx yy
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 1f 00 80 00 02 88 00 00 00 00 74 95 4e 01 c1 c5 e5 02 08 c5 e5 02 08 ... 45 xx yy
Marker | | |Cm|Size |Address |UploadSize |Checksum |512 data bytes |Ix |Chksm |
*/
SSPMInitSend();
SspmBuffer[16] = SSPM_FUNC_UPLOAD_DATA; // 0x1F
Sspm->command_sequence++;
SspmBuffer[543] = Sspm->command_sequence;
SSPMSend(546);
}
void SSPMSendUploadDone(void) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 00 21 00 00 46 32 da
Marker | | |Cm|Size |Ix|Chksm|
*/
SSPMSendCmnd(SSPM_FUNC_UPLOAD_DONE); // 0x21
}
void SSPMSendGetNew1(uint32_t module) {
/*
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
aa 55 01 6b 7e 32 37 39 37 34 13 4b 35 36 37 00 25 00 00 08 c0 0a
Marker |Module id |Ac|Cm|Size |Ix|Chksm|
*/
if (module >= Sspm->module_max) { return; }
SSPMInitSend();
memcpy(SspmBuffer +3, Sspm->module[SSPMGetMappedModuleId(module)], SSPM_MODULE_NAME_SIZE);
SspmBuffer[16] = SSPM_FUNC_GET_NEW1; // 0x25
Sspm->command_sequence++;
SspmBuffer[19] = Sspm->command_sequence;
SSPMSend(22);
}
/*********************************************************************************************/
void SSPMAddModule(void) {
@ -1051,11 +1140,15 @@ void SSPMHandleReceivedData(void) {
MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR("SSPMOverload"));
Sspm->overload_relay = 255;
} else {
Sspm->module_selected--;
if (Sspm->module_selected > 0) {
SSPMSendGetModuleState(Sspm->module_selected -1);
if (Sspm->main_version > SSPM_MAIN_V1_0_0) {
SSPMSendGetNew1(Sspm->module_selected -1);
} else {
SSPMSendGetScheme(Sspm->module_selected);
Sspm->module_selected--;
if (Sspm->module_selected > 0) {
SSPMSendGetModuleState(Sspm->module_selected -1);
} else {
SSPMSendGetScheme(Sspm->module_selected);
}
}
}
break;
@ -1101,9 +1194,14 @@ void SSPMHandleReceivedData(void) {
case SSPM_FUNC_SET_TIME:
/* 0x0C
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 0c 00 01 00 04 3e 62
v1.2.0: adds response from each 4-relay module
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 0c 00 01 00 19 4c 09
*/
TasmotaGlobal.devices_present = 0;
SSPMSendGetModuleState(Sspm->module_selected -1);
if (0 == SspmBuffer[3]) {
// Discard v1.2.0 additions
TasmotaGlobal.devices_present = 0;
SSPMSendGetModuleState(Sspm->module_selected -1);
}
break;
case SSPM_FUNC_INIT_SCAN:
/* 0x10
@ -1117,7 +1215,11 @@ void SSPMHandleReceivedData(void) {
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 15 00 04 00 01 00 00 01 81 b1
|St|FwVersio|
| | 1.0.0|
v1.2.0:
AA 55 01 00 00 00 00 00 00 00 00 00 00 00 00 80 15 00 04 00 01 02 00 01 41 10
| | 1.2.0|
*/
Sspm->main_version = SspmBuffer[20] << 16 | SspmBuffer[21] << 8 | SspmBuffer[22]; // 0x00010000 or 0x00010200
AddLog(LOG_LEVEL_INFO, PSTR("SPM: Main version %d.%d.%d found"), SspmBuffer[20], SspmBuffer[21], SspmBuffer[22]);
Sspm->mstate = SPM_START_SCAN;
@ -1314,6 +1416,19 @@ void SSPMHandleReceivedData(void) {
*/
// TasmotaGlobal.restart_flag = 2;
break;
case SSPM_FUNC_GET_NEW1:
/* 0x25 v1.2.0
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 21 22 23
AA 55 01 8b 34 32 37 39 37 34 13 4b 35 36 37 80 25 00 01 01 06 98 06
Marker |Module id |Ac|Cm|Size |St|Ix|Chksm|
*/
Sspm->module_selected--;
if (Sspm->module_selected > 0) {
SSPMSendGetModuleState(Sspm->module_selected -1);
} else {
SSPMSendGetScheme(Sspm->module_selected);
}
break;
}
} else {
// Initiated by ARM
@ -1515,6 +1630,7 @@ void SSPMHandleReceivedData(void) {
AddLog(LOG_LEVEL_DEBUG, PSTR("SPM: Relay scan done - none found"));
Sspm->mstate = SPM_NONE;
Sspm->error_led_blinks = 255;
}
SSPMSendSetTime();
@ -1755,6 +1871,8 @@ void SSPMEvery100ms(void) {
// Wait for first acknowledge from ARM after reset
SSPMSendCmnd(SSPM_FUNC_FIND);
break;
/*
// Removed to accomodate v1.2.0 too
case SPM_POLL_ARM_SPI:
SSPMSendSPIFind();
Sspm->mstate = SPM_POLL_ARM_2;
@ -1766,6 +1884,7 @@ void SSPMEvery100ms(void) {
case SPM_POLL_ARM_3:
// Wait for second acknowledge from ARM after reset
break;
*/
case SPM_SEND_FUNC_UNITS:
// Get number of units
SSPMSendCmnd(SSPM_FUNC_UNITS);