Merge pull request #12465 from s-hadinger/partition_manager_v1

Add Esp32 Partition Manager as a Berry component
This commit is contained in:
s-hadinger 2021-06-23 22:23:39 +02:00 committed by GitHub
commit 50585788b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1776 additions and 1556 deletions

View File

@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Force ESP32 defines USE_UFILESYS, GUI_TRASH_FILE and #define GUI_EDIT_FILE
- Speed up initial GUI console refresh
- Simplified configuration for ir-full and removal of tasmota-ircustom
- Add Esp32 Partition Manager as a Berry component
### Fixed
- ESP32 Webcam add boundary marker before sending mjpeg image (#12376)

View File

@ -14,6 +14,7 @@ extern int w_webserver_on(bvm *vm);
extern int w_webserver_state(bvm *vm);
extern int w_webserver_check_privileged_access(bvm *vm);
extern int w_webserver_redirect(bvm *vm);
extern int w_webserver_content_start(bvm *vm);
extern int w_webserver_content_send(bvm *vm);
extern int w_webserver_content_send_style(bvm *vm);
@ -23,6 +24,7 @@ extern int w_webserver_content_button(bvm *vm);
extern int w_webserver_argsize(bvm *vm);
extern int w_webserver_arg(bvm *vm);
extern int w_webserver_arg_name(bvm *vm);
extern int w_webserver_has_arg(bvm *vm);
#if !BE_USE_PRECOMPILED_OBJECT
@ -33,6 +35,7 @@ be_native_module_attr_table(webserver) {
be_native_module_function("state", w_webserver_state),
be_native_module_function("check_privileged_access", w_webserver_check_privileged_access),
be_native_module_function("redirect", w_webserver_redirect),
be_native_module_function("content_start", w_webserver_content_start),
be_native_module_function("content_send", w_webserver_content_send),
be_native_module_function("content_send_style", w_webserver_content_send_style),
@ -43,6 +46,7 @@ be_native_module_attr_table(webserver) {
be_native_module_function("arg_size", w_webserver_argsize),
be_native_module_function("arg", w_webserver_arg),
be_native_module_function("arg_name", w_webserver_arg_name),
be_native_module_function("has_arg", w_webserver_has_arg),
};
@ -57,6 +61,7 @@ module webserver (scope: global) {
state, func(w_webserver_state)
check_privileged_access, func(w_webserver_check_privileged_access)
redirect, func(w_webserver_redirect)
content_start, func(w_webserver_content_start)
content_send, func(w_webserver_content_send)
content_send_style, func(w_webserver_content_send_style)
@ -67,6 +72,7 @@ module webserver (scope: global) {
arg_size, func(w_webserver_argsize)
arg, func(w_webserver_arg)
arg_name, func(w_webserver_arg_name)
has_arg, func(w_webserver_has_arg)
}
@const_object_info_end */

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,26 @@
#include "be_constobj.h"
static be_define_const_map_slots(m_libwebserver_map) {
{ be_const_key(on, 5), be_const_func(w_webserver_on) },
{ be_const_key(state, 6), be_const_func(w_webserver_state) },
{ be_const_key(content_flush, -1), be_const_func(w_webserver_content_flush) },
{ be_const_key(content_send, -1), be_const_func(w_webserver_content_send) },
{ be_const_key(arg, -1), be_const_func(w_webserver_arg) },
{ be_const_key(has_arg, -1), be_const_func(w_webserver_has_arg) },
{ be_const_key(content_start, 2), be_const_func(w_webserver_content_start) },
{ be_const_key(state, 5), be_const_func(w_webserver_state) },
{ be_const_key(member, 9), be_const_func(w_webserver_member) },
{ be_const_key(content_button, -1), be_const_func(w_webserver_content_button) },
{ be_const_key(content_start, -1), be_const_func(w_webserver_content_start) },
{ be_const_key(content_send, -1), be_const_func(w_webserver_content_send) },
{ be_const_key(content_flush, -1), be_const_func(w_webserver_content_flush) },
{ be_const_key(redirect, 8), be_const_func(w_webserver_redirect) },
{ be_const_key(content_send_style, -1), be_const_func(w_webserver_content_send_style) },
{ be_const_key(check_privileged_access, 12), be_const_func(w_webserver_check_privileged_access) },
{ be_const_key(arg, -1), be_const_func(w_webserver_arg) },
{ be_const_key(content_stop, -1), be_const_func(w_webserver_content_stop) },
{ be_const_key(member, -1), be_const_func(w_webserver_member) },
{ be_const_key(arg_name, -1), be_const_func(w_webserver_arg_name) },
{ be_const_key(has_arg, -1), be_const_func(w_webserver_has_arg) },
{ be_const_key(arg_size, -1), be_const_func(w_webserver_argsize) },
{ be_const_key(check_privileged_access, 11), be_const_func(w_webserver_check_privileged_access) },
{ be_const_key(on, 3), be_const_func(w_webserver_on) },
};
static be_define_const_map(
m_libwebserver_map,
13
15
);
static be_define_const_module(

View File

@ -6,6 +6,7 @@ partition = module('partition')
import flash
import string
import webserver
#- remove trailing NULL chars from a buffer before converting to string -#
#- Berry strings can contain NULL, but this messes up C-Berry interface -#
@ -80,8 +81,6 @@ class Partition_info
var flags
def init(raw)
import string
if raw == nil || !issubclass(bytes, raw)
self.type = 0
self.subtype = 0
@ -132,7 +131,7 @@ class Partition_info
try
var addr = self.start
var magic_byte = flash.read(addr, 1).get(0, 1)
if magic_byte != 0xE9 raise "internal_error", string.format("Invalid magic_byte 0x%02X (should be 0xE9)", magic_byte) end
if magic_byte != 0xE9 return -1 end
var seg_count = flash.read(addr+1, 1).get(0, 1)
# print("Segment count", seg_count)
@ -161,7 +160,6 @@ class Partition_info
end
def tostring()
import string
var type_s = ""
var subtype_s = ""
if self.type == 0 type_s = "app"
@ -392,6 +390,13 @@ class Partition
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
@ -417,6 +422,11 @@ class Partition
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
@ -477,6 +487,277 @@ class Partition
end
end
#-------------------------------------------------------------
- Parser manager for ESP32
-
-------------------------------------------------------------#
class Partition_manager : Driver
def init()
end
# create a method for adding a button to the main menu
# the button 'Partition Manager' redirects to '/part_mgr?'
def web_add_button()
webserver.content_send(
"<form id=but_part_mgr style='display: block;' action='part_mgr' method='get'><button>Partition Manager</button></form>")
end
#- ---------------------------------------------------------------------- -#
# Show a single OTA Partition
#- ---------------------------------------------------------------------- -#
def page_show_partition(slot, active, ota_num)
#- define `bdis` style for gray disabled buttons -#
webserver.content_send("<fieldset><style>.bdis{background:#888;}.bdis:hover{background:#888;}</style>")
webserver.content_send(string.format("<legend><b title='Start: 0x%03X 000'>&nbsp;%s%s</b></legend>",
slot.start / 0x1000, slot.label, active ? " (active)" : ""))
webserver.content_send(string.format("<p><b>Partition size: </b>%i KB</p>", slot.size / 1024))
var used = slot.get_image_size()
if used >= 0
webserver.content_send(string.format("<p><b>Used: </b>%i KB</p>", used / 1024))
webserver.content_send(string.format("<p><b>Free: </b>%i KB</p>", (slot.size - used) / 1024))
else
webserver.content_send("<p><b>Used</b>: unknwon</p>")
webserver.content_send("<p><b>Free</b>: unknwon</p>")
end
if !active && used >= 0
webserver.content_send("<p><form id=setactive style='display: block;' action='/part_mgr' method='post' ")
webserver.content_send("onsubmit='return confirm(\"This will change the active partition and cause a restart.\");'>")
webserver.content_send("<button name='setactive' class='button bgrn'>Switch To This Partition</button>")
webserver.content_send(string.format("<input name='ota' type='hidden' value='%d'>", ota_num))
webserver.content_send("</form></p>")
else
# put a fake disabled button
webserver.content_send("<p><form style='display: block;'>")
if used >= 0
webserver.content_send("<button name='setactive' class='button bdis' disabled title=\"No need to click, it's already the active partition\">Current Active Partition</button>")
else
webserver.content_send("<button name='setactive' class='button bdis' disabled>Empty Partition</button>")
end
webserver.content_send("</form></p>")
end
webserver.content_send("<p></p></fieldset><p></p>")
end
#- ---------------------------------------------------------------------- -#
# Show a single OTA Partition
#- ---------------------------------------------------------------------- -#
def page_show_spiffs(slot, free_mem)
webserver.content_send(string.format("<fieldset><legend><b title='Start: 0x%03X 000'>&nbsp;%s</b></legend>",
slot.start / 0x1000, slot.label))
webserver.content_send(string.format("<p><b>Partition size:</b> %i KB</p>", slot.size / 1024))
if free_mem != nil
webserver.content_send(string.format("<p><b>Max size: </b>%i KB</p>", (slot.size + free_mem) / 1024))
webserver.content_send(string.format("<p><b>Unallocated: </b>%i KB</p>", free_mem / 1024))
end
#- display Resize button -#
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("<p></p></fieldset><p></p>")
end
#- ---------------------------------------------------------------------- -#
#- Show each partition one after the other - only OTA and SPIFFS
#- ---------------------------------------------------------------------- -#
def page_show_partitions(p)
# display ota partitions
for slot: p.slots
# is the slot app?
var ota_num = slot.is_ota()
if ota_num != nil
# we have an OTA partition
self.page_show_partition(slot, ota_num == p.otadata.active_otadata, ota_num)
elif slot.is_spiffs()
var flash_size = tasmota.memory()['flash'] * 1024
var used_size = (slot.start + slot.size)
self.page_show_spiffs(slot, slot == p.slots[-1] ? flash_size - used_size : nil)
end
end
end
#- ---------------------------------------------------------------------- -#
#- Display the Re-partition section
#- ---------------------------------------------------------------------- -#
def page_show_repartition(p)
if p.get_active() != 0
webserver.content_send("<p style='width:320px;'>Re-partition can be done only if 'app0' is active.</p>")
else
# we can proceed
var app0 = p.get_ota_slot(0)
var app0_size_kb = ((app0.size / 1024 + 63) / 64) * 64 # rounded to upper 64kb
var app0_used_kb = (((app0.get_image_size()) / 1024 / 64) + 1) * 64
var flash_size_kb = tasmota.memory()['flash']
var app_size_max = 1984 # Max OTA size (4096 - 64) / 2 rounded to lowest 64KB
webserver.content_send("<p><b>Resize app Partitions</b></p>")
webserver.content_send(string.format("<p><b>Min:</b> %i KB</p>", app0_used_kb))
webserver.content_send(string.format("<p><b>Max:</b> %i KB</p>", app_size_max))
webserver.content_send("<p><b>New:</b> (multiple of 64 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='%d' max='%d' step='64' name='repartition' value='%i'>", app0_used_kb, app_size_max, app0_size_kb))
webserver.content_send("<p></p><button name='resize' class='button bred'>Resize Partitions</button></form></p>")
end
end
#- this method displays the web page -#
def page_part_mgr()
if !webserver.check_privileged_access() return nil end
var p = partition.Partition()
webserver.content_start("Partition Manager") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -#
# webserver.content_send("<p style='width:340px;'><b style='color:#f56'>Warning:</b> This can brick your device. Don't use unless you know what you are doing.</p>")
webserver.content_send("<fieldset><legend><b>&nbsp;Partition Manager</b></legend><p></p>")
webserver.content_send("<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>")
webserver.content_send("<fieldset><legend><b>&nbsp;Re-partition</b></legend><p></p>")
self.page_show_repartition(p)
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
#- ---------------------------------------------------------------------- -#
#- this is the controller, called using POST and changing parameters
#- ---------------------------------------------------------------------- -#
def page_part_ctl()
if !webserver.check_privileged_access() return nil end
#- check that the partition is valid -#
var p = partition.Partition()
try
if webserver.has_arg("ota")
#- OTA switch partition -#
var ota_target = int(webserver.arg("ota"))
if ota_target < 0 || ota_target > p.ota_max() raise "value_error", string.format("Invalid partition #%d", ota_target) end
var ota_slot = p.get_ota_slot(ota_target)
if ota_slot == nil || ota_slot.get_image_size() < 0
raise "value_error", string.format("Invalid OTA slot #%d", ota_target)
end
print(string.format("Trying to change active partition to %d", ota_target))
#- do the change -#
p.set_active(ota_target)
p.otadata.save() #- write to disk -#
#- and force restart -#
webserver.redirect("/?rst=")
elif webserver.has_arg("spiffs_size")
#- SPIFFS size change -#
var spiffs_size_kb = int(webserver.arg("spiffs_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
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)
end
if spiffs_size_kb == spiffs_slot.size/1024 raise "value_error", "SPIFFS size unchanged, abort" end
#- write the new SPIFFS partition size -#
spiffs_slot.size = spiffs_size_kb * 1024
p.save()
p.invalidate_spiffs() # erase SPIFFS or data is corrupt
#- and force restart -#
webserver.redirect("/?rst=")
elif webserver.has_arg("repartition")
if p.get_active() != 0 raise "value_error", "Can't repartition unless active partition is app0" end
#- complete repartition -#
var app0 = p.get_ota_slot(0)
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 app0 == nil || app1 == nil
raise "internal_error", "Unable to find partitions app0 and app1"
end
if p.ota_max() != 1
raise "internal_error", "There are more than 2 OTA partition, abort"
end
var app0_size_kb = ((app0.size / 1024 + 63) / 64) * 64 # rounded to upper 64kb
var app0_used_kb = (((app0.get_image_size()) / 1024 / 64) + 1) * 64
var flash_size_kb = tasmota.memory()['flash']
var app_size_max = 1984 # Max OTA size (4096 - 64) / 2 rounded to lowest 64KB
var part_size_kb = int(webserver.arg("repartition"))
if part_size_kb < app0_used_kb || part_size_kb > app_size_max
raise "value_error", string.printf("Invalid partition size %i KB, should be between %i and %i", part_size_kb, app0_used_kb, app_size_max)
end
if part_size_kb == app0_size_kb raise "value_error", "No change to partition size, abort" end
#- all good, proceed -#
# resize app0
app0.size = part_size_kb * 1024
# change app1
app1.start = app0.start + app0.size
app1.size = part_size_kb * 1024
# change spiffs
spiffs.start = app1.start + app1.size
spiffs.size = flash_size_kb * 1024 - spiffs.start
p.save()
p.invalidate_spiffs() # erase SPIFFS or data is corrupt
#- and force restart -#
webserver.redirect("/?rst=")
else
raise "value_error", "Unknown command"
end
except .. as e, m
print(string.format("BRY: Exception> '%s' - %s", e, m))
#- 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
#- ---------------------------------------------------------------------- -#
# 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()
#- we need to register a closure, not just a function, that captures the current instance -#
webserver.on("/part_mgr", / -> self.page_part_mgr(), webserver.HTTP_GET)
webserver.on("/part_mgr", / -> self.page_part_ctl(), webserver.HTTP_POST)
end
end
#- create and register driver in Tasmota -#
partition_manager = Partition_manager()
tasmota.add_driver(partition_manager)
## can be removed if put in 'autoexec.bat'
partition_manager.web_add_handler()
partition.Partition = Partition
return partition

Binary file not shown.

View File

@ -1,104 +0,0 @@
#-------------------------------------------------------------
- Parser manager for ESP32
-
-------------------------------------------------------------#
import flash
import string
import partition
import webserver
class Partition_manager : Driver
def init()
end
# create a method for adding a button to the main menu
# the button 'Partition Manager' redirects to '/part_mgr?'
def web_add_button()
webserver.content_send(
"<form id=but_part_mgr style='display: block;' action='part_mgr' method='get'><button>Partition Manager</button></form>")
end
#
# Show a single OTA Partition
#
def page_show_partition(slot, active)
webserver.content_send(string.format("<fieldset><legend><b title='Start: 0x%03X 000'>&nbsp;%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))
var used = slot.get_image_size()
if used >= 0
webserver.content_send(string.format("<p><b>Used: </b>%i KB</p>", used / 1024))
webserver.content_send(string.format("<p><b>Free: </b>%i KB</p>", (slot.size - used) / 1024))
else
webserver.content_send("<p><b>Used: unknwon")
webserver.content_send("<p><b>Free: unknwon")
end
webserver.content_send("<p></p></fieldset><p></p>")
end
#
# Show a single OTA Partition
#
def page_show_spiffs(slot, free_mem)
webserver.content_send(string.format("<fieldset><legend><b title='Start: 0x%03X 000'>&nbsp;%s</b></legend>",
slot.start / 0x1000, slot.label))
webserver.content_send(string.format("<p><b>Partition size: </b>%i KB</p>", slot.size / 1024))
if free_mem != nil
webserver.content_send(string.format("<p><b>Unallocated: </b>%i KB</p>", free_mem / 1024))
end
webserver.content_send("<p></p></fieldset><p></p>")
end
def page_show_partitions()
var p = partition.Partition()
# display ota partitions
for slot: p.slots
# is the slot app?
var ota_num = slot.is_ota()
if ota_num != nil
# we have an OTA partition
self.page_show_partition(slot, ota_num == p.otadata.active_otadata)
elif slot.is_spiffs()
var flash_size = tasmota.memory()['flash'] * 1024
var used_size = (slot.start + slot.size)
self.page_show_spiffs(slot, slot == p.slots[-1] ? flash_size - used_size : nil)
end
end
end
#- this method displays the web page -#
def page_part_mgr()
if !webserver.check_privileged_access() return nil end
webserver.content_start("Partition Manager") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -#
webserver.content_send("<fieldset><legend><b>&nbsp;Partition Manager</b></legend><p></p>")
self.page_show_partitions()
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
#- this is called at Tasmota start-up, as soon as Wifi/Eth is up and web server running -#
def web_add_handler()
#- we need to register a closure, not just a function, that captures the current instance -#
webserver.on("/part_mgr", / -> self.page_part_mgr())
end
end
#- create and register driver in Tasmota -#
partition_manager = Partition_manager()
tasmota.add_driver(partition_manager)
## to be removed if put in 'autoexec.bat'
partition_manager.web_add_handler()

View File

@ -128,6 +128,21 @@ extern "C" {
be_return(vm);
}
// Berry: `webserver.redirect(string) -> nil`
//
int32_t w_webserver_redirect(struct bvm *vm);
int32_t w_webserver_redirect(struct bvm *vm) {
int32_t argc = be_top(vm); // Get the number of arguments
if (argc >= 1 && be_isstring(vm, 1)) {
const char * uri = be_tostring(vm, 1);
Webserver->sendHeader("Location", uri, true);
Webserver->send(302, "text/plain", "");
// Webserver->sendHeader(F("Location"), String(F("http://")) + Webserver->client().localIP().toString(), true);
be_return_nil(vm);
}
be_raise(vm, kTypeError, nullptr);
}
// Berry: `webserver.content_start() -> nil`
//
int32_t w_webserver_content_start(struct bvm *vm);
@ -141,7 +156,7 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
// Berry: `webserver.content_send() -> nil`
// Berry: `webserver.content_send(string) -> nil`
//
int32_t w_webserver_content_send(struct bvm *vm);
int32_t w_webserver_content_send(struct bvm *vm) {
@ -223,6 +238,20 @@ extern "C" {
be_raise(vm, kTypeError, nullptr);
}
// Berry: `webserver.arg_name(int) -> string`
//
// takes an int (index 0..args-1)
int32_t w_webserver_arg_name(struct bvm *vm);
int32_t w_webserver_arg_name(struct bvm *vm) {
int32_t argc = be_top(vm); // Get the number of arguments
if (argc >= 1 && be_isint(vm, 1)) {
int32_t idx = be_toint(vm, 1);
be_pushstring(vm, Webserver->argName(idx).c_str());
be_return(vm);
}
be_raise(vm, kTypeError, nullptr);
}
// Berry: `webserver.has_arg(name:string) -> bool`
//
int32_t w_webserver_has_arg(struct bvm *vm);