Extension Manager, replacing loading of Partition Wizard (#23955)

This commit is contained in:
s-hadinger 2025-09-27 17:20:34 +02:00 committed by GitHub
parent 8a5a12b48d
commit f2006566d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1727 additions and 1521 deletions

View File

@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
- Extend state JSON message with functional hostname and ipaddress which could be WiFi or Ethernet - Extend state JSON message with functional hostname and ipaddress which could be WiFi or Ethernet
- Berry multiplication between string and int (#23850) - Berry multiplication between string and int (#23850)
- Support for RX8030 RTC (#23855) - Support for RX8030 RTC (#23855)
- Extension Manager, replacing loading of Partition Wizard
### Breaking Changed ### Breaking Changed
- Berry `animate` framework is DEPRECATED, will be replace by `animation` framework (#23854) - Berry `animate` framework is DEPRECATED, will be replace by `animation` framework (#23854)

View File

@ -2,7 +2,6 @@
# #
# #
var extension_manager = module("extension_manager") var extension_manager = module("extension_manager")
#@ solidify:extension_manager #@ solidify:extension_manager
@ -40,10 +39,10 @@ class Extension_manager
# tapp_name(wd) # tapp_name(wd)
# #
# Takes a working directory 'wd' and extract the name of the tapp file. # Takes a working directory 'wd' and extract the name of the tapp file.
# Ex: '/.extensions/Leds_Panel.tapp#' becomes 'Leds_Panel.tapp' # Ex: '/.extensions/Leds_Panel.tapp#' becomes 'Leds_Panel'
# #
# @param wv: string - the Working Dir of the tapp file like '/.extensions/Leds_Panel.tapp#' # @param wv: string - the Working Dir of the tapp file like '/.extensions/Leds_Panel.tapp#'
# @return string - the raw name of the tapp file, like 'Leds_Panel.tapp' # @return string - the raw name of the tapp file, like 'Leds_Panel'
##################################################################################################### #####################################################################################################
static def tapp_name(wd) static def tapp_name(wd)
import string import string
@ -113,6 +112,10 @@ class Extension_manager
# Return a map of installed tap files, by tapp file name # Return a map of installed tap files, by tapp file name
# tapp_name -> path {'Leds_Panel.tapp': '/.extensions/Leds_Panel.tapp'} # tapp_name -> path {'Leds_Panel.tapp': '/.extensions/Leds_Panel.tapp'}
# #
# Example:
# > extension_manager.list_installed_ext()
# {'Leds_Panel': '/.extensions/Leds_Panel.tapp', 'Partition_Wizard': '/.extensions/Partition_Wizard.tapp_'}
#
# @return map: tapp_namt -> full path (or wd) # @return map: tapp_namt -> full path (or wd)
static def list_installed_ext() static def list_installed_ext()
# Read extensions in file system # Read extensions in file system
@ -130,6 +133,10 @@ class Extension_manager
# #
# List all extensions in file-system, whether they are running or not # List all extensions in file-system, whether they are running or not
# #
# Example:
# > extension_manager.list_extensions_in_fs()
# {'Leds Panel': '/.extensions/Leds_Panel.tapp', 'Partition Wizard': '/.extensions/Partition_Wizard.tapp_'}
#
# @return sortedmap: with Name of App as key, and following map: # @return sortedmap: with Name of App as key, and following map:
# name, description, version (int), autorun (bool) # name, description, version (int), autorun (bool)
static def list_extensions_in_fs() static def list_extensions_in_fs()
@ -195,19 +202,97 @@ class Extension_manager
# all good, created successfully # all good, created successfully
end end
#####################################################################################################
# run_stop_ext(tapp_fname, run_stop)
#
# @param tapp_fname : string - name of tapp file to install from repository (ex: "Leds_Panel")
# @param run_stop : bool - `true` to run , `false` to stop
# @return bool - `true` if success
static def run_stop_ext(tapp_fname, run_stop)
# sanitize
tapp_fname = _class.tapp_name(tapp_fname)
# get the path for actual file
var tapp_path = _class.list_installed_ext().find(tapp_fname)
if tapp_path != nil
if run_stop
return tasmota.load(tapp_path)
else
return tasmota.unload_extension(tapp_path)
end
else
return false
end
end
#####################################################################################################
# enable_disable_ext(tapp_fname, run_stop)
#
# @param tapp_fname : string - name of tapp file to enable or disable (ex: "Leds_Panel")
# @param enable : bool - `true` to enable , `false` to disable
# @return bool - `true` if success
static def enable_disable_ext(tapp_fname, enable)
import string
# sanitize
tapp_fname = _class.tapp_name(tapp_fname)
# get the path for actual file
var tapp_path = _class.list_installed_ext().find(tapp_fname)
if tapp_path != nil
var new_name
if enable && string.endswith(tapp_path, ".tapp_")
new_name = tapp_path[0..-2] # remove trailing '_'
elif !enable && string.endswith(tapp_path, ".tapp")
new_name = tapp_path + '_' # add trailing '_'
end
if new_name
import path
var success = path.rename(tapp_path, new_name)
if (success) # update any running extension with its new name
if (tasmota._ext != nil) && tasmota._ext.contains(tapp_path)
tasmota._ext[new_name] = tasmota._ext[tapp_path]
tasmota._ext.remove(tapp_path)
end
end
return success
end
end
return false
end
#####################################################################################################
# delete_ext(tapp_fname)
#
# @param tapp_fname : string - name of tapp file to delete from file system (ex: "Leds_Panel")
# @return bool - `true` if success
static def delete_ext(tapp_fname)
# sanitize
tapp_fname = _class.tapp_name(tapp_fname)
# get the path for actual file
var tapp_path = _class.list_installed_ext().find(tapp_fname)
if tapp_path != nil
import path
_class.run_stop_ext(tapp_fname, false) # stop the extension if it's running
var success = path.remove(tapp_path)
return success
else
return false
end
end
##################################################################################################### #####################################################################################################
# install_from_store(tapp_fname) # install_from_store(tapp_fname)
# #
# @param tapp_fname : string - name of tapp file to install from repository # @param tapp_fname : string - name of tapp file to install from repository
# @return bool : 'true' if success
def install_from_store(tapp_fname) def install_from_store(tapp_fname)
import string import string
import path import path
# sanitize # sanitize
tapp_fname = self.tapp_name(tapp_fname) tapp_fname = self.tapp_name(tapp_fname) + ".tapp"
# add '.tapp' extension if it is not present
if !string.endswith(tapp_fname, ".tapp")
tapp_fname += '.tapp'
end
# full url # full url
var ext_url = f"{self.EXT_REPO}{self.EXT_REPO_FOLDER}{tapp_fname}" var ext_url = f"{self.EXT_REPO}{self.EXT_REPO_FOLDER}{tapp_fname}"
log(f"EXT: installing from '{ext_url}'", 3) log(f"EXT: installing from '{ext_url}'", 3)
@ -222,19 +307,20 @@ class Extension_manager
var r = cl.GET() var r = cl.GET()
if r != 200 if r != 200
log(f"EXT: return_code={r}", 2) log(f"EXT: return_code={r}", 2)
return return false
end end
var ret = cl.write_file(local_file) var ret = cl.write_file(local_file)
cl.close() cl.close()
# test if file exists and tell its size # test if file exists and tell its size
if ret > 0 && path.exists(local_file) if ret > 0 && path.exists(local_file)
log(f"EXT: successfully installed '{local_file}' {ret} bytes", 3) log(f"EXT: successfully installed '{local_file}' {ret} bytes", 3)
return true
else else
raise "io_error", f"could not download into '{local_file}' ret={ret}" raise "io_error", f"could not download into '{local_file}' ret={ret}"
end end
except .. as e, m except .. as e, m
tasmota.log(format("EXT: exception '%s' - '%s'", e, m), 2) log(format("EXT: exception '%s' - '%s'", e, m), 2)
return nil return false
end end
end end
@ -278,7 +364,6 @@ class Extension_manager
webserver.content_send("<script>" webserver.content_send("<script>"
"function loadext() {" "function loadext() {"
"eb('store').disabled=true;" "eb('store').disabled=true;"
# "eb('store').innerHTML = '[ <span style=\"color:var(--c_btnsv);\">Loading from Store...</span> ]';"
"x=new XMLHttpRequest();" "x=new XMLHttpRequest();"
"x.timeout=4000;" "x.timeout=4000;"
"x.onreadystatechange = () => {" "x.onreadystatechange = () => {"
@ -286,8 +371,6 @@ class Extension_manager
"if(x.status==200){" "if(x.status==200){"
"eb('inet').style.display='none';" "eb('inet').style.display='none';"
"eb('store').outerHTML=x.responseText;" "eb('store').outerHTML=x.responseText;"
# "}else{"
# "eb('store').innerHTML='<b>[ <span style=\"color:var(--c_btnrsthvr);\">Error loading manifest.</span> ]</b>';"
"}" "}"
"}" "}"
"};" "};"
@ -327,23 +410,23 @@ class Extension_manager
"</script>" "</script>"
) )
webserver.content_send("<fieldset style='padding:0 5px;'>" webserver.content_send(
"<style>" "<fieldset style='padding:0 5px;'>"
# Fix for small text - the key is width: min-content on parent */ "<style>"
".ext-item{width:min-content;min-width:100%;}" # Fix for small text - the key is width: min-content on parent */
".ext-item small{display:block;word-wrap:break-word;overflow-wrap:break-word;white-space:normal;padding-right:5px;padding-top:2px;}" ".ext-item{width:min-content;min-width:100%;}"
# Control bar styles ".ext-item small{display:block;word-wrap:break-word;overflow-wrap:break-word;white-space:normal;padding-right:5px;padding-top:2px;}"
".ext-controls{display:flex;gap:8px;align-items:center;margin-top:4px;padding:0px}" # Control bar styles
# Small action buttons ".ext-controls{display:flex;gap:8px;align-items:center;margin-top:4px;padding:0px}"
".btn-small{padding:0 6px;line-height:1.8rem;font-size:0.9rem;min-width:auto;width:auto;flex-shrink:0;}" # Small action buttons
# form ".btn-small{padding:0 6px;line-height:1.8rem;font-size:0.9rem;min-width:auto;width:auto;flex-shrink:0;}"
"form{padding-top:0px;padding-bottom:0px;}" # form
# Running indicator "form{padding-top:0px;padding-bottom:0px;}"
".running-indicator{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:8px;background:var(--c_btnsvhvr);animation:pulse 1.5s infinite;}" # Running indicator
"@keyframes pulse{0%{opacity:1;}50%{opacity:0.5;}100%{opacity:1;}}" ".running-indicator{display:inline-block;width:8px;height:8px;border-radius:50%;margin-right:8px;background:var(--c_btnsvhvr);animation:pulse 1.5s infinite;}"
"@keyframes pulse{0%{opacity:1;}50%{opacity:0.5;}100%{opacity:1;}}"
# for store # for store
# /* Extension Store specific styles */ # /* Extension Store specific styles */
".store-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;}" ".store-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px;}"
".store-stats{font-size:0.9em;color:var(--c_in);}" ".store-stats{font-size:0.9em;color:var(--c_in);}"
@ -379,24 +462,23 @@ class Extension_manager
while ext_nb < size(installed_ext) while ext_nb < size(installed_ext)
if (ext_nb > 0) webserver.content_send("<hr style='margin:2px 0 0 0;'>") end if (ext_nb > 0) webserver.content_send("<hr style='margin:2px 0 0 0;'>") end
var ext_path = installed_ext.get_by_index(ext_nb) # ex: '/.extensions/Partition_Wizard.tapp' var ext_path = installed_ext.get_by_index(ext_nb) # ex: '/.extensions/Partition_Wizard.tapp'
var ext_path_html = webserver.html_escape(ext_path) var tapp_name = self.tapp_name(ext_path)
var tapp_name_html = webserver.html_escape(tapp_name)
var details = tasmota.read_extension_manifest(ext_path) var details = tasmota.read_extension_manifest(ext_path)
# log(f"EXT: {details=}")
var running = tasmota._ext ? tasmota._ext.contains(ext_path) : false var running = tasmota._ext ? tasmota._ext.contains(ext_path) : false
var running_indicator = running ? " <span class='running-indicator' title='Running'></span>" : "" var running_indicator = running ? " <span class='running-indicator' title='Running'></span>" : ""
var autorun = details.find("autorun", false) var autorun = details.find("autorun", false)
var back_green = "style='background:var(--c_btnsvhvr);'" var back_green = "style='background:var(--c_btnsvhvr);'"
var dark_blue = "style='background:var(--c_btnoff);'" var dark_blue = "style='background:var(--c_btnoff);'"
webserver.content_send("<div class='ext-item'>") webserver.content_send("<div class='ext-item'>")
webserver.content_send(f"<span title='path: {ext_path_html}'><b>{webserver.html_escape(details['name'])}</b>{running_indicator}</span><br>") webserver.content_send(f"<span title='path: {tapp_name_html}'><b>{webserver.html_escape(details['name'])}</b>{running_indicator}</span><br>")
webserver.content_send(f"<small>{webserver.html_escape(details['description'])}</small>") webserver.content_send(f"<small>{webserver.html_escape(details['description'])}</small>")
webserver.content_send("<div class='ext-controls' style='padding-top:0px;padding-bottom:0px;'>") webserver.content_send("<div class='ext-controls' style='padding-top:0px;padding-bottom:0px;'>")
webserver.content_send("<form action='/ext' method='post' class='ext-controls'>") webserver.content_send("<form action='/ext' method='post' class='ext-controls'>")
webserver.content_send(f"<button type='submit' class='btn-small' {running ? back_green :: dark_blue} name='{running ? 's' :: 'r'}{ext_path_html}'>{running ? 'Running' :: 'Stopped'}</button>") webserver.content_send(f"<button type='submit' class='btn-small' {running ? back_green :: dark_blue} name='{running ? 's' :: 'r'}{tapp_name_html}'>{running ? 'Running' :: 'Stopped'}</button>")
webserver.content_send(f"<button type='submit' class='btn-small' {autorun ? '' :: dark_blue} name='{autorun ? 'a' :: 'A'}{ext_path_html}'>Auto-run: {autorun ? 'ON' :: 'OFF'}</button>") webserver.content_send(f"<button type='submit' class='btn-small' {autorun ? '' :: dark_blue} name='{autorun ? 'a' :: 'A'}{tapp_name_html}'>Auto-run: {autorun ? 'ON' :: 'OFF'}</button>")
webserver.content_send(f"<button type='submit' class='btn-small bred' name='d{ext_path_html}' onclick='return confirm(\"Confirm deletion of {webserver.html_escape(ext_path)}\")'>Uninstall</button>") webserver.content_send(f"<button type='submit' class='btn-small bred' name='d{tapp_name_html}' onclick='return confirm(\"Confirm deletion of {tapp_name_html}.tapp\")'>Uninstall</button>")
# webserver.content_send(f"<button type='submit' class='btn-small' style='background-color:var(--c_btnoff);border-color:var(--c_btnrst);border-width:3px;border-style:solid;' name='d{ext_path_html}' onclick='return confirm(\"Confirm deletion of {webserver.html_escape(ext_path)}\")'>Uninstall</button>")
webserver.content_send("</form></div></div>") webserver.content_send("</form></div></div>")
ext_nb += 1 ext_nb += 1
@ -412,7 +494,6 @@ class Extension_manager
"<hr style='margin-bottom:0;'>" "<hr style='margin-bottom:0;'>"
"<span id='inet' style='font-size:small;font-weight:normal;''>&nbsp;(This feature requires an internet connection)</span>" "<span id='inet' style='font-size:small;font-weight:normal;''>&nbsp;(This feature requires an internet connection)</span>"
"</h3></div>") "</h3></div>")
# "<p><small>&nbsp;(This feature requires an internet connection)</small></p>")
webserver.content_send("<b id='store'>[ <span style='color:var(--c_btnsv);'>Loading from Store...</span> ]</b>") webserver.content_send("<b id='store'>[ <span style='color:var(--c_btnsv);'>Loading from Store...</span> ]</b>")
@ -483,10 +564,8 @@ class Extension_manager
installed_tapp_name = self.tapp_name(entry['file']) installed_tapp_name = self.tapp_name(entry['file'])
var installed_tapp_name_web = webserver.html_escape(installed_tapp_name) var installed_tapp_name_web = webserver.html_escape(installed_tapp_name)
installed = installed_ext.contains(installed_tapp_name) installed = installed_ext.contains(installed_tapp_name)
var installed_path_web
if installed if installed
var installed_path = installed_ext[installed_tapp_name] var installed_path = installed_ext[installed_tapp_name]
installed_path_web = webserver.html_escape(installed_path)
var details = tasmota.read_extension_manifest(installed_path) var details = tasmota.read_extension_manifest(installed_path)
installed_version = int(details.find('version', 0)) installed_version = int(details.find('version', 0))
end end
@ -524,14 +603,14 @@ class Extension_manager
"<div style='width:30%'></div>") "<div style='width:30%'></div>")
if installed if installed
if upgrade if upgrade
webserver.content_send( f"<button type='submit' class='btn-action' name='u{installed_path_web}' onclick='return confirm(\"Confirm upgrade of {installed_path_web}\")'>Upgrade</button>") webserver.content_send( f"<button type='submit' class='btn-action' name='u{installed_tapp_name_web}' onclick='return confirm(\"Confirm upgrade of {installed_tapp_name_web}\")'>Upgrade</button>")
else else
webserver.content_send( "<button type='submit' class='btn-action' style='visibility:hidden;'></button>") webserver.content_send( "<button type='submit' class='btn-action' style='visibility:hidden;'></button>")
end end
webserver.content_send( f"<button type='submit' class='btn-action bred' name='d{installed_path_web}' onclick='return confirm(\"Confirm deletion of {installed_path_web}\")'>Uninstall</button>") webserver.content_send( f"<button type='submit' class='btn-action bred' name='d{installed_tapp_name_web}' onclick='return confirm(\"Confirm deletion of {installed_tapp_name_web}\")'>Uninstall</button>")
else else
webserver.content_send( f"<button type='submit' class='btn-action' style='visibility:hidden;'></button>" webserver.content_send( f"<button type='submit' class='btn-action' name='i{installed_tapp_name_web}' onclick='return confirm(\"Confirm installation of {app_name_web}\")'>Install</button>"
"<button type='submit' class='btn-action bgrn' name='i{installed_tapp_name_web}' onclick='return confirm(\"Confirm installation of {app_name_web}\")'>Install</button>") "<button type='submit' class='btn-action bgrn' name='I{installed_tapp_name_web}' onclick='return confirm(\"Confirm installation of {app_name_web}\")'>Install+Run</button>")
end end
webserver.content_send( "</form>" webserver.content_send( "</form>"
"</div>" "</div>"
@ -565,14 +644,14 @@ class Extension_manager
cl.begin(url) cl.begin(url)
var r = cl.GET() var r = cl.GET()
if r != 200 if r != 200
tasmota.log(f"EXT: error fetching manifest {r}", 2) log(f"EXT: error fetching manifest {r}", 2)
raise "webclient_error", f"Error fetching manifest code={r}" raise "webclient_error", f"Error fetching manifest code={r}"
end end
var s = cl.get_string() var s = cl.get_string()
cl.close() cl.close()
return s return s
except .. as e, m except .. as e, m
tasmota.log(format("EXT: exception '%s' - '%s'", e, m), 2) log(format("EXT: exception '%s' - '%s'", e, m), 2)
raise e, m raise e, m
end end
end end
@ -591,73 +670,46 @@ class Extension_manager
try try
# log(f">>> {webserver.arg_name(0)=} {webserver.arg(0)=} {webserver.arg_size()=}") # log(f">>> {webserver.arg_name(0)=} {webserver.arg(0)=} {webserver.arg_size()=}")
# var redirect_to_store = false # add suffix to redirect to store
var btn_name = webserver.arg_name(0) var btn_name = webserver.arg_name(0)
var action = btn_name[0] # first character var action = btn_name[0] # first character
var action_path = btn_name[1..] # remove first character var action_path = btn_name[1..] # remove first character
if (action == "r") # button "Run" if (action == "r") || (action == "s") # button "Run" or "Stop"
if (action_path != "") self.run_stop_ext(action_path, action == "r")
# log(f"EXT: run '{action_path}'")
tasmota.load(action_path)
end
elif (action == "s") # button "Stop"
# log(f"EXT: stop '{action_path}'")
tasmota.unload_extension(action_path)
elif (action == "a") || (action == "A") # button "Autorun", "A" enable, "a" disable elif (action == "a") || (action == "A") # button "Autorun", "A" enable, "a" disable
var new_name self.enable_disable_ext(action_path, action == "A")
if (action == "a") && string.endswith(action_path, ".tapp") # Autorun is enabled, disable it
new_name = action_path[0..-5] + "tapp_"
elif (action == "A") && string.endswith(action_path, ".tapp_")
new_name = action_path[0..-6] + "tapp"
end
if new_name
var success = path.rename(action_path, new_name)
# log(f"EXT: rename '{action_path}' to '{new_name} {success=}", 3)
if (success) # update any running extension with its new name
if tasmota._ext.contains(action_path)
tasmota._ext[new_name] = tasmota._ext[action_path]
tasmota._ext.remove(action_path)
end
end
else
log(f"EXT: wrong action '{btn_name}'", 3)
end
elif (action == 'd') # button "Delete" elif (action == 'd') # button "Delete"
if (action_path != "") self.delete_ext(action_path)
# first stop if it was running
tasmota.unload_extension(action_path)
# then delete file
var success = path.remove(action_path)
# log(f"EXT: delete '{action_path}' {success=}", 3)
end
# Now try the store commands # Now try the store commands
elif (action == 'u') # Upgrade ext elif (action == 'u') # Upgrade ext
# log(f"EXT: upgrade '{action_path}'", 3)
# first stop the app if it's running # first stop the app if it's running
tasmota.unload_extension(action_path) self.run_stop_ext(action_path, false) # stop the extension
self.install_from_store(self.tapp_name(action_path)) var success = self.install_from_store(self.tapp_name(action_path))
# redirect_to_store = true elif (action == 'i') || (action == 'I') # Install ext ('I' for run as well)
elif (action == 'i') # Install ext var success = self.install_from_store(self.tapp_name(action_path))
# log(f"EXT: install '{action_path}'", 3) if success
self.install_from_store(self.tapp_name(action_path)) if (action == 'I') # run
# redirect_to_store = true self.run_stop_ext(action_path, true)
else # disable
self.enable_disable_ext(action_path, false)
end
end
else
log(f"EXT: wrong action '{btn_name}'", 3)
end end
# var redirect_suffix = redirect_to_store ? "store=" : ""
# webserver.redirect(f"/ext?{redirect_suffix}")
webserver.redirect(f"/ext") webserver.redirect(f"/ext")
except .. as e, m except .. as e, m
log(f"CFG: Exception> '{e}' - {m}", 2) log(f"EXT: Exception> '{e}' - {m}", 2)
#- display error page -# #- display error page -#
webserver.content_start("Parameter error") #- title of the web page -# webserver.content_start("Parameter error") #- title of the web page -#
webserver.content_send_style() #- send standard Tasmota styles -# webserver.content_send_style() #- send standard Tasmota styles -#
webserver.content_send(f"<p style='width:340px;'><b>Exception:</b><br>'{webserver.html_escape(e)}'<br>{webserver.html_escape(m)}</p>") webserver.content_send(f"<p style='width:340px;'><b>Exception:</b><br>'{webserver.html_escape(e)}'<br>{webserver.html_escape(m)}</p>")
webserver.content_button(webserver.BUTTON_CONFIGURATION) #- button back to management page -# webserver.content_button(webserver.BUTTON_MANAGEMENT) #- button back to management page -#
webserver.content_stop() #- end of web page -# webserver.content_stop() #- end of web page -#
end end
end end

View File

@ -948,7 +948,7 @@ class Tasmota
end end
def unload_extension(name_or_instance) def unload_extension(name_or_instance)
if (self._ext == nil) return end if (self._ext == nil) return false end
var d = name_or_instance # d = driver var d = name_or_instance # d = driver
if type(name_or_instance) == 'string' if type(name_or_instance) == 'string'
@ -961,11 +961,14 @@ class Tasmota
d.unload() d.unload()
end end
self.remove_driver(d) self.remove_driver(d)
# force gc of instance
name_or_instance = nil
d = nil
tasmota.gc()
return true
else
return false
end end
# force gc of instance
name_or_instance = nil
d = nil
tasmota.gc()
end end
# cmd high-level function # cmd high-level function

View File

@ -1893,45 +1893,51 @@ be_local_closure(class_Tasmota_unload_extension, /* name */
&be_ktab_class_Tasmota, /* shared constants */ &be_ktab_class_Tasmota, /* shared constants */
&be_const_str_unload_extension, &be_const_str_unload_extension,
&be_const_str_solidified, &be_const_str_solidified,
( &(const binstruction[38]) { /* code */ ( &(const binstruction[44]) { /* code */
0x8808014B, // 0000 GETMBR R2 R0 K75 0x8808014B, // 0000 GETMBR R2 R0 K75
0x4C0C0000, // 0001 LDNIL R3 0x4C0C0000, // 0001 LDNIL R3
0x1C080403, // 0002 EQ R2 R2 R3 0x1C080403, // 0002 EQ R2 R2 R3
0x780A0000, // 0003 JMPF R2 #0005 0x780A0001, // 0003 JMPF R2 #0006
0x80000400, // 0004 RET 0 0x50080000, // 0004 LDBOOL R2 0 0
0x5C080200, // 0005 MOVE R2 R1 0x80040400, // 0005 RET 1 R2
0x600C0004, // 0006 GETGBL R3 G4 0x5C080200, // 0006 MOVE R2 R1
0x5C100200, // 0007 MOVE R4 R1 0x600C0004, // 0007 GETGBL R3 G4
0x7C0C0200, // 0008 CALL R3 1 0x5C100200, // 0008 MOVE R4 R1
0x1C0C0701, // 0009 EQ R3 R3 K1 0x7C0C0200, // 0009 CALL R3 1
0x780E0004, // 000A JMPF R3 #0010 0x1C0C0701, // 000A EQ R3 R3 K1
0x880C014B, // 000B GETMBR R3 R0 K75 0x780E0004, // 000B JMPF R3 #0011
0x8C0C070C, // 000C GETMET R3 R3 K12 0x880C014B, // 000C GETMBR R3 R0 K75
0x5C140200, // 000D MOVE R5 R1 0x8C0C070C, // 000D GETMET R3 R3 K12
0x7C0C0400, // 000E CALL R3 2 0x5C140200, // 000E MOVE R5 R1
0x5C080600, // 000F MOVE R2 R3 0x7C0C0400, // 000F CALL R3 2
0x600C0004, // 0010 GETGBL R3 G4 0x5C080600, // 0010 MOVE R2 R3
0x5C100400, // 0011 MOVE R4 R2 0x600C0004, // 0011 GETGBL R3 G4
0x7C0C0200, // 0012 CALL R3 1 0x5C100400, // 0012 MOVE R4 R2
0x1C0C0757, // 0013 EQ R3 R3 K87 0x7C0C0200, // 0013 CALL R3 1
0x780E000A, // 0014 JMPF R3 #0020 0x1C0C0757, // 0014 EQ R3 R3 K87
0xA40EE000, // 0015 IMPORT R3 K112 0x780E0012, // 0015 JMPF R3 #0029
0x8C10075A, // 0016 GETMET R4 R3 K90 0xA40EE000, // 0016 IMPORT R3 K112
0x5C180400, // 0017 MOVE R6 R2 0x8C10075A, // 0017 GETMET R4 R3 K90
0x581C007A, // 0018 LDCONST R7 K122 0x5C180400, // 0018 MOVE R6 R2
0x7C100600, // 0019 CALL R4 3 0x581C007A, // 0019 LDCONST R7 K122
0x78120001, // 001A JMPF R4 #001D 0x7C100600, // 001A CALL R4 3
0x8C10057A, // 001B GETMET R4 R2 K122 0x78120001, // 001B JMPF R4 #001E
0x7C100200, // 001C CALL R4 1 0x8C10057A, // 001C GETMET R4 R2 K122
0x8C10017B, // 001D GETMET R4 R0 K123 0x7C100200, // 001D CALL R4 1
0x5C180400, // 001E MOVE R6 R2 0x8C10017B, // 001E GETMET R4 R0 K123
0x7C100400, // 001F CALL R4 2 0x5C180400, // 001F MOVE R6 R2
0x4C040000, // 0020 LDNIL R1 0x7C100400, // 0020 CALL R4 2
0x4C080000, // 0021 LDNIL R2 0x4C040000, // 0021 LDNIL R1
0xB80E0400, // 0022 GETNGBL R3 K2 0x4C080000, // 0022 LDNIL R2
0x8C0C077C, // 0023 GETMET R3 R3 K124 0xB8120400, // 0023 GETNGBL R4 K2
0x7C0C0200, // 0024 CALL R3 1 0x8C10097C, // 0024 GETMET R4 R4 K124
0x80000000, // 0025 RET 0 0x7C100200, // 0025 CALL R4 1
0x50100200, // 0026 LDBOOL R4 1 0
0x80040800, // 0027 RET 1 R4
0x70020001, // 0028 JMP #002B
0x500C0000, // 0029 LDBOOL R3 0 0
0x80040600, // 002A RET 1 R3
0x80000000, // 002B RET 0
}) })
) )
); );

View File

@ -49,7 +49,7 @@ default_envs =
[tasmota] [tasmota]
; *** Global build / unbuild compile time flags for ALL Tasmota / Tasmota32 [env] ; *** Global build / unbuild compile time flags for ALL Tasmota / Tasmota32 [env]
;build_unflags = ;build_unflags =
build_flags = -DUSE_BERRY_PARTITION_WIZARD ;build_flags =
[env] [env]
;build_unflags = ${common.build_unflags} ;build_unflags = ${common.build_unflags}

View File

@ -1221,7 +1221,7 @@
//#define USE_WEBCAM // Add support for webcam //#define USE_WEBCAM // Add support for webcam
#define USE_AUTOCONF // Enable Esp32(x) autoconf feature, requires USE_BERRY and USE_WEBCLIENT_HTTPS (12KB Flash) #define USE_AUTOCONF // Enable Esp32(x) autoconf feature, requires USE_BERRY and USE_WEBCLIENT_HTTPS (12KB Flash)
// #define USE_EXTENSION_MANAGER // Enable Esp32(x) extensions manager, requires USE_BERRY and USE_WEBCLIENT_HTTPS (9KB Flash) #define USE_EXTENSION_MANAGER // Enable Esp32(x) extensions manager, requires USE_BERRY and USE_WEBCLIENT_HTTPS (11KB Flash)
#define USE_BERRY // Enable Berry scripting language #define USE_BERRY // Enable Berry scripting language
#define USE_BERRY_PYTHON_COMPAT // Enable by default `import python_compat` #define USE_BERRY_PYTHON_COMPAT // Enable by default `import python_compat`
#define USE_BERRY_TIMEOUT 4000 // Timeout in ms, will raise an exception if running time exceeds this timeout #define USE_BERRY_TIMEOUT 4000 // Timeout in ms, will raise an exception if running time exceeds this timeout
@ -1235,9 +1235,9 @@
// Note that only two ciphers are enabled: ECDHE_RSA_WITH_AES_128_GCM_SHA256, ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 // Note that only two ciphers are enabled: ECDHE_RSA_WITH_AES_128_GCM_SHA256, ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
#define USE_BERRY_WEBCLIENT_USERAGENT "TasmotaClient" // default user-agent used, can be changed with `wc.set_useragent()` #define USE_BERRY_WEBCLIENT_USERAGENT "TasmotaClient" // default user-agent used, can be changed with `wc.set_useragent()`
#define USE_BERRY_WEBCLIENT_TIMEOUT 2000 // Default timeout in milliseconds #define USE_BERRY_WEBCLIENT_TIMEOUT 2000 // Default timeout in milliseconds
#define USE_BERRY_LEDS_PANEL // Add button to dynamically load the Leds Panel from a bec file online // #define USE_BERRY_LEDS_PANEL // Add button to dynamically load the Leds Panel from a bec file online
#define USE_BERRY_LEDS_PANEL_URL "http://ota.tasmota.com/tapp/leds_panel.bec" #define USE_BERRY_LEDS_PANEL_URL "http://ota.tasmota.com/tapp/leds_panel.bec"
#define USE_BERRY_LVGL_PANEL // Add button to dynamically load the LVGL Panel from a bec file online // #define USE_BERRY_LVGL_PANEL // Add button to dynamically load the LVGL Panel from a bec file online
#define USE_BERRY_LVGL_PANEL_URL "http://ota.tasmota.com/tapp/lvgl_panel.bec" #define USE_BERRY_LVGL_PANEL_URL "http://ota.tasmota.com/tapp/lvgl_panel.bec"
//#define USE_BERRY_PARTITION_WIZARD // Add a button to dynamically load the Partion Wizard from a bec file online (+1.3KB Flash) //#define USE_BERRY_PARTITION_WIZARD // Add a button to dynamically load the Partion Wizard from a bec file online (+1.3KB Flash)
#define USE_BERRY_PARTITION_WIZARD_URL "http://ota.tasmota.com/tapp/partition_wizard.bec" #define USE_BERRY_PARTITION_WIZARD_URL "http://ota.tasmota.com/tapp/partition_wizard.bec"