SBbJ8N~wUF?wFfWs~h-RZ_&
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
E0ghrA{Qv*}
diff --git a/tasmota/berry/modules/Partition_Wizard/partition_wizard.bec b/tasmota/berry/modules/Partition_Wizard/partition_wizard.bec
index b5227ecd65055926674eeb041663a4415a1eb4dc..786b64901bbf01062b5a4d76c16a467b72ff6a90 100644
GIT binary patch
literal 17101
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
delta 5630
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
OcbsQ-9*M%ezx-c?mG|NR
diff --git a/tasmota/berry/modules/partition_wizard.be b/tasmota/berry/modules/partition_wizard.be
index abda19956..9db989901 100644
--- a/tasmota/berry/modules/partition_wizard.be
+++ b/tasmota/berry/modules/partition_wizard.be
@@ -24,25 +24,49 @@ class Partition_wizard_UI
if persist.find("factory_migrate") == true
# remove marker to avoid bootloop if something goes wrong
+ tasmota.log("UPL: Resuming after step 1", 2)
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.remove_rule("parwiz_5s1") # first remove rule to avoid firing it again at Wifi reconnect
+ tasmota.remove_rule("parwiz_5s2") # 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")
+ tasmota.add_rule("Wifi#Connected=1", continue_after_5s, "parwiz_5s1")
+ tasmota.add_rule("Wifi#Connected==1", continue_after_5s, "parwiz_5s2")
end
end
+ # ----------------------------------------------------------------------
+ # Patch partition core since we can't chang the solidified code
+ # ----------------------------------------------------------------------
+ def patch_partition_core(p)
+ var otadata = p.otadata
+
+ # patch load
+ import flash
+ var otadata0 = flash.read(otadata.offset, 32)
+ var otadata1 = flash.read(otadata.offset + 0x1000, 32)
+ otadata.seq0 = otadata0.get(0, 4) #- ota_seq for block 1 -#
+ otadata.seq1 = otadata1.get(0, 4) #- ota_seq for block 2 -#
+ var valid0 = otadata0.get(28, 4) == otadata.crc32_ota_seq(otadata.seq0) #- is CRC32 valid? -#
+ var valid1 = otadata1.get(28, 4) == otadata.crc32_ota_seq(otadata.seq1) #- is CRC32 valid? -#
+ if !valid0 otadata.seq0 = nil end
+ if !valid1 otadata.seq1 = nil end
+
+ otadata._validate()
+ 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 format(self._default_safeboot_URL, arch_sub)
+ return string.format(self._default_safeboot_URL, arch_sub)
end
# create a method for adding a button to the main menu
@@ -53,15 +77,34 @@ class Partition_wizard_UI
"")
end
+ # ----------------------------------------------------------------------
+ # Get last fs
+ #
+ # Get the last fs partition
+ # Return the actual slot
+ # ----------------------------------------------------------------------
+ def get_last_fs(p)
+ var sz = size(p.slots)
+ var idx = 1
+ while idx < sz
+ var slot = p.slots[-idx]
+ if slot.is_spiffs()
+ return slot
+ end
+ idx += 1
+ end
+ return nil
+ end
+
#- ---------------------------------------------------------------------- -#
#- Get fs unallocated size
#- ---------------------------------------------------------------------- -#
def get_unallocated_k(p)
- var last_slot = p.slots[-1]
- if last_slot.is_spiffs()
+ var last_fs = self.get_last_fs(p)
+ if last_fs != nil
# 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.sz) / 1024 # last kb used for fs
+ var partition_end_k = (last_fs.start + last_fs.sz) / 1024 # last kb used for fs
if partition_end_k < flash_size_k
return flash_size_k - partition_end_k
end
@@ -73,8 +116,8 @@ class Partition_wizard_UI
#- 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
+ var last_fs = p.slots[-1]
+ if last_fs != nil # verify that last slot is filesystem
# get end of previous partition slot
var last_app = p.slots[-2]
# round upper 64kB
@@ -85,7 +128,7 @@ class Partition_wizard_UI
end
#- ---------------------------------------------------------------------- -#
- #- Get max falsh size
+ #- Get max flash size
#
# Takes into account that the flash size written may not be accurate
# and the flash chip may be larger
@@ -99,15 +142,40 @@ class Partition_wizard_UI
return flash_size_k
end
+ # ----------------------------------------------------------------------
+ # Remove any non wanted partion after last FS
+ # ----------------------------------------------------------------------
+ def remove_partition_after_last_fs(p)
+ # remove any partition after last fs
+ do
+ var last_fs = self.get_last_fs(p)
+ var changed = false
+ if last_fs != nil
+ while true
+ var last_slot = p.slots[-1]
+ if !last_slot.is_spiffs() && (last_slot.type != 0)
+ p.slots.remove(size(p.slots) - 1) # remove last slot
+ changed = true
+ else
+ break
+ end
+ end
+ if changed p.save() end
+ end
+ end
+ end
+
#- ---------------------------------------------------------------------- -#
#- Resize flash definition if needed
#- ---------------------------------------------------------------------- -#
def resize_max_flash_size_k(p)
+ self.remove_partition_after_last_fs(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
@@ -131,7 +199,7 @@ class Partition_wizard_UI
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(format("UPL: changing flash definition from 0x02X to 0x%02X", old_def, flash_def[3]), 3)
+ 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
@@ -142,9 +210,9 @@ class Partition_wizard_UI
#- 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.sz + 1023) / 1024
+ var last_fs = p.slots[-1]
+ if last_fs != nil
+ return (last_fs.sz + 1023) / 1024
end
return 0
end
@@ -171,13 +239,14 @@ class Partition_wizard_UI
#- ---------------------------------------------------------------------- -#
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("
")
webserver.content_send("")
@@ -231,7 +300,7 @@ class Partition_wizard_UI
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
+ if self.get_last_fs(p) == nil return false end
return true # device does not have factory partition
end
@@ -254,11 +323,12 @@ class Partition_wizard_UI
# - 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 format("active_otadata=%i", cur_part) end # unsupported configuration
+ 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)
@@ -314,6 +384,7 @@ class Partition_wizard_UI
# `app0` changed subtype to `factory`
# `app1` moved to right after `factory` and resized
# `app1` changed subtype to `app0` and renamed `app0`
+ # remove any partition past the last fs
#
# Returns:
# - false if READY
@@ -345,10 +416,11 @@ class Partition_wizard_UI
static def copy_ota(from_addr, to_addr, sz)
import flash
+ import string
var size_left = sz
var offset = 0
- tasmota.log(format("UPL: Copy flash from 0x%06X to 0x%06X (size: %ikB)", from_addr, to_addr, sz / 1024), 2)
+ tasmota.log(string.format("UPL: Copy flash from 0x%06X to 0x%06X (size: %ikB)", from_addr, to_addr, sz / 1024), 2)
while size_left > 0
var b = flash.read(from_addr + offset, 4096)
flash.erase(to_addr + offset, 4096)
@@ -356,13 +428,14 @@ class Partition_wizard_UI
size_left -= 4096
offset += 4096
if ((offset-4096) / 102400) < (offset / 102400)
- tasmota.log(format("UPL: Progress %ikB", offset/1024), 3)
+ tasmota.log(string.format("UPL: Progress %ikB", offset/1024), 3)
end
end
tasmota.log("UPL: done", 2)
end
def do_step_1(p)
+ import persist
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
@@ -377,11 +450,15 @@ class Partition_wizard_UI
p.set_active(1)
p.save()
+ persist.factory_migrate = true
+ persist.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)
@@ -399,7 +476,7 @@ class Partition_wizard_UI
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(format("UPL: flashing `safeboot` from %s %ikB", safeboot_url, (safeboot_size / 1024) + 1), 2)
+ 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)
@@ -415,6 +492,9 @@ class Partition_wizard_UI
if step3_state == true return true end
if type(step3_state) == 'string' raise "internal_error", step3_state end
+ # remove any partition after last fs
+ self.remove_partition_after_last_fs(p)
+
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
@@ -479,6 +559,7 @@ class Partition_wizard_UI
def show_migrate_to_factory(p)
# display ota partitions
import webserver
+ import string
if !self.factory_migrate_eligible(p) return end
@@ -488,20 +569,20 @@ class Partition_wizard_UI
webserver.content_send("
")
@@ -515,6 +596,7 @@ class Partition_wizard_UI
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("")
webserver.content_button(webserver.BUTTON_MANAGEMENT) #- button back to management page -#
@@ -710,24 +795,23 @@ class Partition_wizard_UI
def do_safeboot_partitioning()
import webserver
import partition_core
+ import string
var p = partition_core.Partition() # load partition layout
+ self.patch_partition_core(p)
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(format("UPL: error (%s) %s", e, m), 2)
+ 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)
@@ -742,7 +826,7 @@ class Partition_wizard_UI
try
self.do_step_2(p, safeboot_url)
except .. as e, m
- tasmota.log(format("UPL: error (%s) %s", e, m), 2)
+ tasmota.log(string.format("UPL: error (%s) %s", e, m), 2)
return m
end
end
@@ -756,7 +840,7 @@ class Partition_wizard_UI
try
self.do_step_3(p)
except .. as e, m
- tasmota.log(format("UPL: error (%s) %s", e, m), 2)
+ tasmota.log(string.format("UPL: error (%s) %s", e, m), 2)
return m
end
end