mirror of https://github.com/arendst/Tasmota.git
Merge branch 'development' into rtsp_auth
This commit is contained in:
commit
02927be009
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -42,10 +42,12 @@ jobs:
|
|||
- tasmota32-lvgl
|
||||
- tasmota32s2
|
||||
- tasmota32c3
|
||||
- tasmota32c3usb
|
||||
- tasmota32solo1
|
||||
- tasmota32solo1-safeboot
|
||||
- tasmota32-safeboot
|
||||
- tasmota32c3-safeboot
|
||||
- tasmota32c3usb-safeboot
|
||||
- tasmota32s2-safeboot
|
||||
- tasmota32s3-safeboot
|
||||
steps:
|
||||
|
|
15
CHANGELOG.md
15
CHANGELOG.md
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
|
@ -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;
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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
|
@ -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
|
||||
})
|
||||
)
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" } },
|
||||
|
|
|
@ -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)" } },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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.
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
# rm Partition_wizard.tapp; zip -j -0 Partition_wizard.tapp Partition_Wizard/*
|
||||
import partition_wizard
|
Binary file not shown.
|
@ -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'> %s%s</b></legend>",
|
||||
webserver.content_send(string.format("<legend><b title='Start: 0x%03X 000'> %s%s </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'> %s</b></legend>",
|
||||
slot.start / 0x1000, slot.label))
|
||||
webserver.content_send(string.format("<fieldset><legend><b title='Start: 0x%03X 000'> filesystem </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> Partition Manager</b></legend><p></p>")
|
||||
webserver.content_send("<fieldset><legend><b> Partition Manager </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> Re-partition</b></legend><p></p>")
|
||||
webserver.content_send("<fieldset><legend><b> Re-partition </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
|
||||
|
|
|
@ -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> Resize FS to max </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> Resize FS </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> Migrate to safeboot partition layout </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> </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> Current partitions </b></legend><p></p><table>")
|
||||
|
||||
# don't show <sys> portion
|
||||
#webserver.content_send("<tr><td title='offset:0x000000 size:0x010000'><sys>: </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>: </td><td align='right'> %i KB </td><td> (%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>: </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'><free>: </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.
Binary file not shown.
|
@ -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
|
Binary file not shown.
|
@ -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
|
||||
|
|
Binary file not shown.
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 אינדקס"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 색인"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "Індекс УФ"
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 "紫外线指数"
|
||||
|
|
|
@ -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 "紫外線指數"
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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_
|
||||
|
|
|
@ -353,18 +353,22 @@ const char HTTP_FORM_UPG[] PROGMEM =
|
|||
"</fieldset><br><br>"
|
||||
"<fieldset><legend><b> " D_UPGRADE_BY_FILE_UPLOAD " </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 = "&|>|<|"|'|\";
|
||||
|
||||
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 = "&|>|<|"|'|\";
|
||||
|
||||
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 ")); // 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 ")); // 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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*********************************************************************************************\
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue