diff --git a/.gitmodules b/.gitmodules
index 84a5e304aa..85003018ee 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -63,7 +63,7 @@
 	ignore = dirty
 [submodule "rboot"]
 	path = Sming/Components/rboot/rboot
-	url = https://github.com/raburton/rboot.git
+	url = https://github.com/mikee47/rboot
 	ignore = dirty
 [submodule "spiffs"]
 	path = Sming/Components/spiffs/spiffs
diff --git a/Sming/Arch/Esp8266/Components/esp8266/startup.cpp b/Sming/Arch/Esp8266/Components/esp8266/startup.cpp
index ef6d1d07d3..ff62f084d2 100644
--- a/Sming/Arch/Esp8266/Components/esp8266/startup.cpp
+++ b/Sming/Arch/Esp8266/Components/esp8266/startup.cpp
@@ -55,9 +55,8 @@ extern "C" void WEAK_ATTR user_rf_pre_init(void)
 
 extern "C" uint32 ICACHE_FLASH_ATTR  WEAK_ATTR user_rf_cal_sector_set(void)
 {
-	// RF calibration stored in last sector of sysParam
-	auto sysParam = *Storage::findPartition(Storage::Partition::SubType::Data::sysParam);
-	return ((sysParam.address() + sysParam.size()) / SPI_FLASH_SEC_SIZE) - 1;
+	auto rfCal = *Storage::findPartition(Storage::Partition::SubType::Data::rfCal);
+	return rfCal.address();
 }
 
 #ifdef SDK_INTERNAL
@@ -71,16 +70,14 @@ extern "C" void ICACHE_FLASH_ATTR WEAK_ATTR user_pre_init(void)
 	Storage::initialize();
 
 	auto sysParam = *Storage::findPartition(Storage::Partition::SubType::Data::sysParam);
+	auto rfCal = *Storage::findPartition(Storage::Partition::SubType::Data::rfCal);
 	auto phy = *Storage::findPartition(Storage::Partition::SubType::Data::phy);
 
-	// RF calibration stored in last sector of sysParam
-	auto sysParamSize = sysParam.size() - SPI_FLASH_SEC_SIZE;
-
 	static const partition_item_t partitions[] = {
 			{SYSTEM_PARTITION_BOOTLOADER,		0,						SPI_FLASH_SEC_SIZE},
 			{SYSTEM_PARTITION_PHY_DATA,			phy.address(),			phy.size()},
-			{SYSTEM_PARTITION_SYSTEM_PARAMETER,	sysParam.address(),		sysParamSize},
-			{SYSTEM_PARTITION_RF_CAL,			sysParam.address() + sysParamSize,	SPI_FLASH_SEC_SIZE},
+			{SYSTEM_PARTITION_SYSTEM_PARAMETER,	sysParam.address(),		sysParam.size()},
+			{SYSTEM_PARTITION_RF_CAL,			rfCal.address(),		rfCal.size()},
 	};
 
 	enum flash_size_map sizeMap = system_get_flash_size_map();
diff --git a/Sming/Arch/Esp8266/options.json b/Sming/Arch/Esp8266/options.json
index 7e3d545649..dfa35550eb 100644
--- a/Sming/Arch/Esp8266/options.json
+++ b/Sming/Arch/Esp8266/options.json
@@ -6,5 +6,23 @@
                 "filename": "$(FLASH_INIT_DATA_VCC)"
             }
         }
+    },
+    "alternate": {
+        "description": "ESP8266 layout with critical partitions at start of flash",
+        "partition_table_offset": "0x2000",
+        "partitions": {
+            "rf_cal": {
+                "address": "0x3000"
+            },
+            "phy_init": {
+                "address": "0x4000"
+            },
+            "sys_param": {
+                "address": "0x5000"
+            },
+            "rom0": {
+                "address": "0x8000"
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/Sming/Arch/Esp8266/spiffs-two-roms.hw b/Sming/Arch/Esp8266/spiffs-two-roms.hw
index 9503891e2a..93e4605b47 100644
--- a/Sming/Arch/Esp8266/spiffs-two-roms.hw
+++ b/Sming/Arch/Esp8266/spiffs-two-roms.hw
@@ -2,11 +2,14 @@
 	"name": "Two ROM slots with single SPIFFS",
 	"base_config": "spiffs",
 	"partitions": {
+		"rom0": {
+			"subtype": "ota_0"
+		},
 		"rom1": {
 			"address": "0x108000",
 			"size": "992K",
 			"type": "app",
-			"subtype": "ota_0",
+			"subtype": "ota_1",
 			"filename": "$(RBOOT_ROM_1_BIN)"
 		}
 	}
diff --git a/Sming/Arch/Esp8266/standard.hw b/Sming/Arch/Esp8266/standard.hw
index 240e56f679..37d6bd7d68 100644
--- a/Sming/Arch/Esp8266/standard.hw
+++ b/Sming/Arch/Esp8266/standard.hw
@@ -2,7 +2,7 @@
 	"name": "Standard config with single ROM",
 	"comment": "Should work with any Esp8266 variant",
 	"arch": "Esp8266",
-	"partition_table_offset": "0x2000",
+	"partition_table_offset": "self.devices[0].size - 0x6000",
 	"devices": {
 		"spiFlash": {
 			"type": "flash",
@@ -12,25 +12,31 @@
 		}
 	},
 	"partitions": {
+		"rom0": {
+			"address": "0x002000",
+			"size": "992K",
+			"type": "app",
+			"subtype": "factory",
+			"filename": "$(RBOOT_ROM_0_BIN)"
+		},
+		"rf_cal": {
+			"address": "self.device.size - 0x5000",
+			"size": "4K",
+			"type": "data",
+			"subtype": "rfcal"
+		},
 		"phy_init": {
-			"address": "0x003000",
+			"address": "self.device.size - 0x4000",
 			"size": "4K",
 			"type": "data",
 			"subtype": "phy",
 			"filename": "$(FLASH_INIT_DATA)"
 		},
 		"sys_param": {
-			"address": "0x004000",
-			"size": "16K",
+			"address": "self.device.size - 0x3000",
+			"size": "12K",
 			"type": "data",
 			"subtype": "sysparam"
-		},
-		"rom0": {
-			"address": "0x008000",
-			"size": "992K",
-			"type": "app",
-			"subtype": "factory",
-			"filename": "$(RBOOT_ROM_0_BIN)"
 		}
 	}
-}
+}
\ No newline at end of file
diff --git a/Sming/Arch/Esp8266/two-rom-mode.hw b/Sming/Arch/Esp8266/two-rom-mode.hw
index 9d6399179f..2be8a10eca 100644
--- a/Sming/Arch/Esp8266/two-rom-mode.hw
+++ b/Sming/Arch/Esp8266/two-rom-mode.hw
@@ -3,13 +3,14 @@
 	"base_config": "standard",
 	"partitions": {
 		"rom0": {
+			"subtype": "ota_0",
 			"size": "480K"
 		},
 		"rom1": {
 			"address": "0x80000",
-			"size": "512K",
+			"size": "488K",
 			"type": "app",
-			"subtype": "ota_0",
+			"subtype": "ota_1",
 			"filename": "$(RBOOT_ROM_1_BIN)"
 		}
 	}
diff --git a/Sming/Components/Storage/README.rst b/Sming/Components/Storage/README.rst
index 8b609fee02..0ab3666e67 100644
--- a/Sming/Components/Storage/README.rst
+++ b/Sming/Components/Storage/README.rst
@@ -84,6 +84,16 @@ If using this approach, remember to updated your project's ``component.mk`` with
 and verify the layout is correct using ``make map``.
 
 
+OTA updates
+-----------
+
+When planning OTA updates please check that the displayed partition map corresponds to your project.
+For example, the partition table requires a free sector so must not overlap other partitions.
+
+Your OTA update process must include a step to write the partition table to the correct location.
+
+It is not necessary to update the bootloader. See :component:`rboot` for further information.
+
 
 Custom configurations
 ---------------------
@@ -173,6 +183,19 @@ To customise the hardware configuration for a project, for example 'my_project':
    This will flash everything: bootloader, partition table and all defined partitions (those with a ``filename`` entry).
 
 
+.. note::
+
+   The build system isn't smart enough to track dependencies for partition build targets.
+
+   To rebuild these manually type::
+
+      make partbuild
+
+   These will be removed when ``make clean`` is run, but you can also clean them separately thus::
+
+      make part-clean
+
+
 Partition maps
 --------------
 
@@ -356,7 +379,7 @@ you can take advantage of the partition API to manage them as follows:
 -  Create an instance of your custom device and make a call to :cpp:func:`Storage::registerDevice`
    in your ``init()`` function (or elsewhere if more appropriate).
 
- 
+
 API
 ---
 
diff --git a/Sming/Components/Storage/Tools/hwconfig/config.py b/Sming/Components/Storage/Tools/hwconfig/config.py
index 19bad09f79..67a465bd0c 100644
--- a/Sming/Components/Storage/Tools/hwconfig/config.py
+++ b/Sming/Components/Storage/Tools/hwconfig/config.py
@@ -46,6 +46,7 @@ def from_name(cls, name):
         config.load(name)
         if options != '':
             config.parse_options(options.split(','))
+        config.resolve_expressions()
         config.partitions.sort()
         return config
 
@@ -73,6 +74,9 @@ def parse_options(self, options):
             temp.pop('description', None)
             self.parse_dict(temp)
 
+    def resolve_expressions(self):
+        self.partitions.offset = eval(str(self.partitions.offset))
+
     def parse_dict(self, data):
         base_config = data.pop('base_config', None)
         if base_config is not None:
@@ -89,7 +93,7 @@ def parse_dict(self, data):
             elif k == 'arch':
                 self.arch = v
             elif k == 'partition_table_offset':
-                self.partitions.offset = parse_int(v)
+                self.partitions.offset = v
             elif k == 'devices':
                 self.devices.parse_dict(v)
             elif k == 'comment':
@@ -132,7 +136,7 @@ def buildVars(self):
         return res
 
     def verify(self, secure):
-        self.partitions.verify(self.arch, secure)
+        self.partitions.verify(self.arch, self.devices[0], secure)
 
     def map(self):
         return partition.Map(self.partitions, self.devices)
diff --git a/Sming/Components/Storage/Tools/hwconfig/hwconfig.py b/Sming/Components/Storage/Tools/hwconfig/hwconfig.py
index 7c388ff726..e2b3d90ddb 100644
--- a/Sming/Components/Storage/Tools/hwconfig/hwconfig.py
+++ b/Sming/Components/Storage/Tools/hwconfig/hwconfig.py
@@ -44,7 +44,7 @@ def handle_flashcheck(args, config, part):
     for e in list:
         addr, filename = e.split('=')
         addr = int(addr, 0)
-        part = config.partitions.find_by_address(addr)
+        part = config.partitions.find_by_address(config.devices[0], addr)
         if part is None:
             raise InputError("No partition contains address 0x%08x" % addr)
         if part.address != addr:
@@ -113,5 +113,5 @@ def main():
     try:
         main()
     except InputError as e:
-        print(e, file=sys.stderr)
+        print("** ERROR! %s" % e, file=sys.stderr)
         sys.exit(2)
diff --git a/Sming/Components/Storage/Tools/hwconfig/partition.py b/Sming/Components/Storage/Tools/hwconfig/partition.py
index d0a0e07f1e..18e78435d5 100644
--- a/Sming/Components/Storage/Tools/hwconfig/partition.py
+++ b/Sming/Components/Storage/Tools/hwconfig/partition.py
@@ -16,11 +16,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import struct, hashlib, storage, binascii
+import struct, hashlib, storage, binascii, copy
 from common import *
 
 MAX_PARTITION_LENGTH = 0xC00  # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
 MD5_PARTITION_BEGIN = b"\xEB\xEB" + b"\xFF" * 14  # The first 2 bytes are like magic numbers for MD5 sum
+FLASH_SECTOR_SIZE = 0x1000
 PARTITION_TABLE_SIZE = 0x1000  # Size of partition table
 PARTITION_ENTRY_SIZE = 32
 
@@ -84,6 +85,7 @@
         "nvs_keys": 0x04,
         "efuse": 0x05,
         "sysparam": 0x40,
+        "rfcal": 0x41,
         "esphttpd": 0x80,
         "fat": 0x81,
         "spiffs": 0x82,
@@ -154,6 +156,10 @@ def offset_str(self):
     def buildVars(self):
         dict = {}
         dict['PARTITION_NAMES'] = " ".join(p.name for p in self)
+        buildparts = [p for p in self if p.build is not None]
+        dict['PARTITIONS_WITH_TARGETS'] = " ".join(p.name for p in buildparts)
+        dict['PARTITION_BUILD_TARGETS'] = " ".join(p.filename for p in buildparts)
+
         for p in self:
             dict.update(p.buildVars())
         return dict
@@ -198,19 +204,28 @@ def find_by_name(self, name):
                 return p
         return None
 
-    def find_by_address(self, addr):
+    def find_by_address(self, device, addr):
         for p in self:
-            if p.contains(addr):
+            if p.device == device and p.contains(addr):
                 return p
         return None
 
-    def verify(self, arch, secure):
+    def verify(self, arch, spiFlash, secure):
         """Verify partition layout
         """
         # verify each partition individually
         for p in self:
             p.verify(arch, secure)
 
+        if self.offset % FLASH_SECTOR_SIZE != 0:
+            raise InputError("Partition table offset not aligned to flash sector")
+
+        p = self.find_by_address(spiFlash, self.offset)
+        if p is None:
+            p = self.find_by_address(spiFlash, self.offset + PARTITION_TABLE_SIZE - 1)
+        if not p is None:
+            raise InputError("Partition table conflict with '%s'" % p.name)
+
         # check on duplicate name
         names = [p.name for p in self]
         duplicates = set(n for n in names if names.count(n) > 1)
@@ -224,7 +239,10 @@ def verify(self, arch, secure):
             raise InputError("Partition names must be unique")
 
         # check for overlaps
-        minPartitionAddress = self.offset + PARTITION_TABLE_SIZE
+        if arch == 'Esp32':
+            minPartitionAddress = self.offset + PARTITION_TABLE_SIZE
+        else:
+            minPartitionAddress = 0x00002000
         dev = ''
         last = None
         for p in self:
@@ -345,7 +363,7 @@ def parse_dict(self, data, devices):
                 if k == 'device':
                     self.device = devices.find_by_name(v)
                 elif k == 'address':
-                    self.address = parse_int(v)
+                    self.address = eval(str(v))
                 elif k == 'size':
                     self.size = parse_int(v)
                 elif k == 'filename':
@@ -523,13 +541,17 @@ def add_unused(address, last_end):
             if address > last_end + 1:
                 add('(unused)', last_end + 1, address - last_end - 1)
 
+        partitions = copy.copy(table)
+
         if table.offset == 0:
             last = None
         else:
-            add("Boot Sector", 0, table.offset)
-            last = add("Partition Table", table.offset, PARTITION_TABLE_SIZE)
+            last = add('Boot Sector', 0, min(table.offset, partitions[0].address))
+            p = Entry(device, 'Partition Table', table.offset, PARTITION_TABLE_SIZE, 0xff, 0xff)
+            partitions.append(p)
+            partitions.sort()
 
-        for p in table:
+        for p in partitions:
             if last is not None:
                 if p.device != last.device:
                     add_unused(last.device.size, last.end())
diff --git a/Sming/Components/Storage/component.mk b/Sming/Components/Storage/component.mk
index a6627c056a..986d7fe0d0 100644
--- a/Sming/Components/Storage/component.mk
+++ b/Sming/Components/Storage/component.mk
@@ -35,10 +35,11 @@ HWCONFIG_TOOL := \
 	BUILD_BASE=$(BUILD_BASE) \
 	$(PYTHON) $(PARTITION_TOOLS)/hwconfig/hwconfig.py
 
-ifeq (,$(MAKE_DOCS)$(MAKE_CLEAN))
-
-# Generate build variables from hardware configuration
 HWCONFIG_MK := $(PROJECT_DIR)/$(OUT_BASE)/hwconfig.mk
+ifneq (,$(MAKE_DOCS)$(MAKE_CLEAN))
+-include $(HWCONFIG_MK)
+else
+# Generate build variables from hardware configuration
 $(shell $(HWCONFIG_TOOL) --quiet expr $(HWCONFIG) $(HWCONFIG_MK) "config.buildVars()")
 include $(HWCONFIG_MK)
 ifeq ($(SMING_ARCH_HW),)
@@ -47,6 +48,7 @@ else ifneq ($(SMING_ARCH),$(SMING_ARCH_HW))
 $(error Hardware configuration is for arch $(SMING_ARCH_HW), does not match SMING_ARCH ($(SMING_ARCH)))
 endif
 COMPONENT_CXXFLAGS := -DPARTITION_TABLE_OFFSET=$(PARTITION_TABLE_OFFSET)
+COMPONENT_CPPFLAGS := -DPARTITION_TABLE_OFFSET=$(PARTITION_TABLE_OFFSET)
 
 # Function to evaluate expression against config
 HWEXPR := $(HWCONFIG_TOOL) $(if $(PART),--part "$(PART)") expr $(HWCONFIG) -
@@ -109,22 +111,25 @@ $(PARTITIONS_BIN): $(HWCONFIG_DEPENDS)
 # Create build target for a partition
 # $1 -> Partition name
 define PartitionTarget
-PTARG := $(shell $(HWCONFIG_TOOL) --part $1 expr $(HWCONFIG) - part.filename)
-$$(PTARG):
+$(PARTITION_$1_FILENAME):
 	$$(Q) $$(MAKE) --no-print-directory $$(shell $$(HWCONFIG_TOOL) --part $1 expr $$(HWCONFIG) - "part.build['target']") PART=$1
-CUSTOM_TARGETS += $$(PTARG)
+CUSTOM_TARGETS += $(PARTITION_$1_FILENAME)
 endef
 
-# Create build targets for all partitions with 'build' property
-DEBUG_VARS += PARTITIONS_WITH_TARGETS
-PARTITIONS_WITH_TARGETS := $(call HwExpr,(' '.join([part.name for part in filter(lambda part: part.build is not None, config.partitions)])))
-
 # Must be invoked from project.mk after all Components have been processed
 # This allows partition definitions to include variables which may not yet be defined
 define PartitionCreateTargets
 $(foreach p,$(PARTITIONS_WITH_TARGETS),$(eval $(call PartitionTarget,$p)))
 endef
 
+.PHONY: buildpart
+buildpart: ##Rebuild all partition targets
+ifeq (,$(PARTITION_BUILD_TARGETS))
+	@echo "No partitions have build targets"
+else
+	$(Q) rm -f $(PARTITION_BUILD_TARGETS)
+	$(MAKE) $(PARTITION_BUILD_TARGETS)
+endif
 
 ##@Flashing
 
@@ -192,3 +197,11 @@ flashmap: $(PARTITIONS_BIN) kill_term ##Write partition table to device
 
 
 endif # MAKE_DOCS
+
+##@Cleaning
+
+clean: part-clean
+
+.PHONY: part-clean
+part-clean: ##Clean partition targets
+	$(Q) rm -f $(PARTITION_BUILD_TARGETS)
diff --git a/Sming/Components/Storage/src/include/Storage/Partition.h b/Sming/Components/Storage/src/include/Storage/Partition.h
index 0c141abad0..9b5badeee1 100644
--- a/Sming/Components/Storage/src/include/Storage/Partition.h
+++ b/Sming/Components/Storage/src/include/Storage/Partition.h
@@ -59,6 +59,7 @@
 	XX(nvsKeys, 0x04, "NVS key information")                                                                           \
 	XX(eFuseEm, 0x05, "eFuse emulation")                                                                               \
 	XX(sysParam, 0x40, "System Parameters")                                                                            \
+	XX(rfCal, 0x41, "RF Calibration")                                                                                  \
 	XX(espHttpd, 0x80, "ESPHTTPD")                                                                                     \
 	XX(fat, 0x81, "FAT")                                                                                               \
 	XX(spiffs, 0x82, "SPIFFS")                                                                                         \
diff --git a/Sming/Components/Storage/src/include/Storage/PartitionTable.h b/Sming/Components/Storage/src/include/Storage/PartitionTable.h
index 1dd4ee7b16..5acfc135bd 100644
--- a/Sming/Components/Storage/src/include/Storage/PartitionTable.h
+++ b/Sming/Components/Storage/src/include/Storage/PartitionTable.h
@@ -45,7 +45,7 @@ class PartitionTable
 
 	/**
 	 * @brief Find partition by name
-	 * @param Name to search for, case-sensitive
+	 * @param Name Name to search for, case-sensitive
 	 * @retval Partition
 	 *
 	 * Names are unique so at most only one match
@@ -55,6 +55,26 @@ class PartitionTable
 		return *std::find(begin(), end(), name);
 	}
 
+	/**
+	 * @brief Find partition containing the given address
+	 * @param address Address to search for
+	 * @retval Partition
+	 */
+	Partition find(uint32_t address) const
+	{
+		return *std::find_if(begin(), end(), [address](Partition part) { return part.contains(address); });
+	}
+
+	/**
+	 * @brief Find the n'th OTA partition
+	 */
+	Partition findOta(uint8_t index)
+	{
+		using App = Partition::SubType::App;
+		auto subtype = App(uint8_t(App::ota0) + index);
+		return (subtype >= App::ota_min && subtype <= App::ota_max) ? *find(subtype) : Partition{};
+	}
+
 	Iterator begin() const
 	{
 		return Iterator(mDevice, 0);
diff --git a/Sming/Components/rboot/.patches/rboot.patch b/Sming/Components/rboot/.patches/rboot.patch
deleted file mode 100644
index b8cd1ce078..0000000000
--- a/Sming/Components/rboot/.patches/rboot.patch
+++ /dev/null
@@ -1,402 +0,0 @@
-diff --git a/Makefile b/Makefile
-index 638a8f7..5176f8f 100644
---- a/Makefile
-+++ b/Makefile
-@@ -58,6 +58,19 @@ endif
- ifeq ($(RBOOT_IROM_CHKSUM),1)
- 	CFLAGS += -DBOOT_IROM_CHKSUM
- endif
-+ifneq ($(RBOOT_ROM0_ADDR),)
-+	CFLAGS += -DBOOT_ROM0_ADDR=$(RBOOT_ROM0_ADDR)
-+endif
-+ifneq ($(RBOOT_ROM1_ADDR),)
-+	CFLAGS += -DBOOT_ROM1_ADDR=$(RBOOT_ROM1_ADDR)
-+endif
-+ifneq ($(RBOOT_ROM2_ADDR),)
-+	CFLAGS += -DBOOT_ROM2_ADDR=$(RBOOT_ROM2_ADDR)
-+endif
-+ifeq ($(RBOOT_SILENT),1)
-+	CFLAGS += -DBOOT_SILENT=$(RBOOT_SILENT)
-+endif
-+
- ifneq ($(RBOOT_EXTRA_INCDIR),)
- 	CFLAGS += $(addprefix -I,$(RBOOT_EXTRA_INCDIR))
- endif
-
-diff --git a/rboot.c b/rboot.c
-index d622f97..5e254fa 100644
---- a/rboot.c
-+++ b/rboot.c
-@@ -13,6 +13,12 @@
- #define UART_CLK_FREQ	(26000000 * 2)
- #endif
- 
-+#ifndef BOOT_SILENT
-+#define echof(fmt, ...) ets_printf(fmt, ##__VA_ARGS__)
-+#else
-+#define echof(fmt, ...)
-+#endif
-+
- static uint32_t check_image(uint32_t readpos) {
- 
- 	uint8_t buffer[BUFFER_SIZE];
-@@ -257,8 +263,30 @@ static uint8_t calc_chksum(uint8_t *start, uint8_t *end) {
- // created on first boot or in case of corruption
- static uint8_t default_config(rboot_config *romconf, uint32_t flashsize) {
- 	romconf->count = 2;
-+#ifdef BOOT_ROM0_ADDR
-+    romconf->roms[0] = BOOT_ROM0_ADDR;
-+#else
- 	romconf->roms[0] = SECTOR_SIZE * (BOOT_CONFIG_SECTOR + 1);
-+#endif
-+
-+#ifdef BOOT_ROM1_ADDR
-+	romconf->roms[1] = BOOT_ROM1_ADDR;
-+#else
- 	romconf->roms[1] = (flashsize / 2) + (SECTOR_SIZE * (BOOT_CONFIG_SECTOR + 1));
-+#endif
-+
-+#if defined(BOOT_BIG_FLASH) && defined(BOOT_GPIO_ENABLED)
-+	if(flashsize > 0x200000) {
-+		romconf->count += 1;
-+#ifdef BOOT_ROM2_ADDR
-+	romconf->roms[2] = BOOT_ROM2_ADDR;
-+#else
-+	romconf->roms[2] = 0x310000;
-+#endif
-+	romconf->gpio_rom = 2;
-+}
-+#endif
-+
- #ifdef BOOT_GPIO_ENABLED
- 	romconf->mode = MODE_GPIO_ROM;
- #endif
-@@ -306,100 +334,100 @@ uint32_t NOINLINE find_image(void) {
- 	ets_delay_us(BOOT_DELAY_MICROS);
- #endif
- 
--	ets_printf("\r\nrBoot v1.4.2 - richardaburton@gmail.com\r\n");
-+	echof("\r\nrBoot v1.4.2 - richardaburton@gmail.com\r\n");
- 
- 	// read rom header
- 	SPIRead(0, header, sizeof(rom_header));
- 
- 	// print and get flash size
--	ets_printf("Flash Size:   ");
-+	echof("Flash Size:   ");
- 	flag = header->flags2 >> 4;
- 	if (flag == 0) {
--		ets_printf("4 Mbit\r\n");
-+		echof("4 Mbit\r\n");
- 		flashsize = 0x80000;
- 	} else if (flag == 1) {
--		ets_printf("2 Mbit\r\n");
-+		echof("2 Mbit\r\n");
- 		flashsize = 0x40000;
- 	} else if (flag == 2) {
--		ets_printf("8 Mbit\r\n");
-+		echof("8 Mbit\r\n");
- 		flashsize = 0x100000;
- 	} else if (flag == 3 || flag == 5) {
--		ets_printf("16 Mbit\r\n");
-+		echof("16 Mbit\r\n");
- #ifdef BOOT_BIG_FLASH
- 		flashsize = 0x200000;
- #else
- 		flashsize = 0x100000; // limit to 8Mbit
- #endif
- 	} else if (flag == 4 || flag == 6) {
--		ets_printf("32 Mbit\r\n");
-+		echof("32 Mbit\r\n");
- #ifdef BOOT_BIG_FLASH
- 		flashsize = 0x400000;
- #else
- 		flashsize = 0x100000; // limit to 8Mbit
- #endif
- 	} else if (flag == 8) {
--		ets_printf("64 Mbit\r\n");
-+		echof("64 Mbit\r\n");
- #ifdef BOOT_BIG_FLASH
- 		flashsize = 0x800000;
- #else
- 		flashsize = 0x100000; // limit to 8Mbit
- #endif
- 	} else if (flag == 9) {
--		ets_printf("128 Mbit\r\n");
-+		echof("128 Mbit\r\n");
- #ifdef BOOT_BIG_FLASH
- 		flashsize = 0x1000000;
- #else
- 		flashsize = 0x100000; // limit to 8Mbit
- #endif
- 	} else {
--		ets_printf("unknown\r\n");
-+		echof("unknown\r\n");
- 		// assume at least 4mbit
- 		flashsize = 0x80000;
- 	}
- 
- 	// print spi mode
--	ets_printf("Flash Mode:   ");
-+	echof("Flash Mode:   ");
- 	if (header->flags1 == 0) {
--		ets_printf("QIO\r\n");
-+		echof("QIO\r\n");
- 	} else if (header->flags1 == 1) {
--		ets_printf("QOUT\r\n");
-+		echof("QOUT\r\n");
- 	} else if (header->flags1 == 2) {
--		ets_printf("DIO\r\n");
-+		echof("DIO\r\n");
- 	} else if (header->flags1 == 3) {
--		ets_printf("DOUT\r\n");
-+		echof("DOUT\r\n");
- 	} else {
--		ets_printf("unknown\r\n");
-+		echof("unknown\r\n");
- 	}
- 
- 	// print spi speed
--	ets_printf("Flash Speed:  ");
-+	echof("Flash Speed:  ");
- 	flag = header->flags2 & 0x0f;
--	if (flag == 0) ets_printf("40 MHz\r\n");
--	else if (flag == 1) ets_printf("26.7 MHz\r\n");
--	else if (flag == 2) ets_printf("20 MHz\r\n");
--	else if (flag == 0x0f) ets_printf("80 MHz\r\n");
--	else ets_printf("unknown\r\n");
-+	if (flag == 0) echof("40 MHz\r\n");
-+	else if (flag == 1) echof("26.7 MHz\r\n");
-+	else if (flag == 2) echof("20 MHz\r\n");
-+	else if (flag == 0x0f) echof("80 MHz\r\n");
-+	else echof("unknown\r\n");
- 
- 	// print enabled options
- #ifdef BOOT_BIG_FLASH
--	ets_printf("rBoot Option: Big flash\r\n");
-+	echof("rBoot Option: Big flash\r\n");
- #endif
- #ifdef BOOT_CONFIG_CHKSUM
--	ets_printf("rBoot Option: Config chksum\r\n");
-+	echof("rBoot Option: Config chksum\r\n");
- #endif
- #ifdef BOOT_GPIO_ENABLED
--	ets_printf("rBoot Option: GPIO rom mode (%d)\r\n", BOOT_GPIO_NUM);
-+	echof("rBoot Option: GPIO rom mode (%d)\r\n", BOOT_GPIO_NUM);
- #endif
- #ifdef BOOT_GPIO_SKIP_ENABLED
--	ets_printf("rBoot Option: GPIO skip mode (%d)\r\n", BOOT_GPIO_NUM);
-+	echof("rBoot Option: GPIO skip mode (%d)\r\n", BOOT_GPIO_NUM);
- #endif
- #ifdef BOOT_RTC_ENABLED
--	ets_printf("rBoot Option: RTC data\r\n");
-+	echof("rBoot Option: RTC data\r\n");
- #endif
- #ifdef BOOT_IROM_CHKSUM
--	ets_printf("rBoot Option: irom chksum\r\n");
-+	echof("rBoot Option: irom chksum\r\n");
- #endif
--	ets_printf("\r\n");
-+	echof("\r\n");
- 
- 	// read boot config
- 	SPIRead(BOOT_CONFIG_SECTOR * SECTOR_SIZE, buffer, SECTOR_SIZE);
-@@ -410,7 +438,7 @@ uint32_t NOINLINE find_image(void) {
- #endif
- 		) {
- 		// create a default config for a standard 2 rom setup
--		ets_printf("Writing default boot config.\r\n");
-+		echof("Writing default boot config.\r\n");
- 		ets_memset(romconf, 0x00, sizeof(rboot_config));
- 		romconf->magic = BOOT_CONFIG_MAGIC;
- 		romconf->version = BOOT_CONFIG_VERSION;
-@@ -433,10 +461,10 @@ uint32_t NOINLINE find_image(void) {
- 
- 		if (rtc.next_mode & MODE_TEMP_ROM) {
- 			if (rtc.temp_rom >= romconf->count) {
--				ets_printf("Invalid temp rom selected.\r\n");
-+				echof("Invalid temp rom selected.\r\n");
- 				return 0;
- 			}
--			ets_printf("Booting temp rom.\r\n");
-+			echof("Booting temp rom.\r\n");
- 			temp_boot = 1;
- 			romToBoot = rtc.temp_rom;
- 		}
-@@ -447,10 +475,10 @@ uint32_t NOINLINE find_image(void) {
- 	if (perform_gpio_boot(romconf)) {
- #if defined(BOOT_GPIO_ENABLED)
- 		if (romconf->gpio_rom >= romconf->count) {
--			ets_printf("Invalid GPIO rom selected.\r\n");
-+			echof("Invalid GPIO rom selected.\r\n");
- 			return 0;
- 		}
--		ets_printf("Booting GPIO-selected rom.\r\n");
-+		echof("Booting GPIO-selected rom.\r\n");
- 		romToBoot = romconf->gpio_rom;
- 		gpio_boot = 1;
- #elif defined(BOOT_GPIO_SKIP_ENABLED)
-@@ -462,7 +490,7 @@ uint32_t NOINLINE find_image(void) {
- #endif
- 		updateConfig = 1;
- 		if (romconf->mode & MODE_GPIO_ERASES_SDKCONFIG) {
--			ets_printf("Erasing SDK config sectors before booting.\r\n");
-+			echof("Erasing SDK config sectors before booting.\r\n");
- 			for (sec = 1; sec < 5; sec++) {
- 				SPIEraseSector((flashsize / SECTOR_SIZE) - sec);
- 			}
-@@ -474,7 +502,7 @@ uint32_t NOINLINE find_image(void) {
- 	// gpio/temp boots will have already validated this
- 	if (romconf->current_rom >= romconf->count) {
- 		// if invalid rom selected try rom 0
--		ets_printf("Invalid rom selected, defaulting to 0.\r\n");
-+		echof("Invalid rom selected, defaulting to 0.\r\n");
- 		romToBoot = 0;
- 		romconf->current_rom = 0;
- 		updateConfig = 1;
-@@ -486,14 +514,14 @@ uint32_t NOINLINE find_image(void) {
- #ifdef BOOT_GPIO_ENABLED
- 	if (gpio_boot && loadAddr == 0) {
- 		// don't switch to backup for gpio-selected rom
--		ets_printf("GPIO boot rom (%d) is bad.\r\n", romToBoot);
-+		echof("GPIO boot rom (%d) is bad.\r\n", romToBoot);
- 		return 0;
- 	}
- #endif
- #ifdef BOOT_RTC_ENABLED
- 	if (temp_boot && loadAddr == 0) {
- 		// don't switch to backup for temp rom
--		ets_printf("Temp boot rom (%d) is bad.\r\n", romToBoot);
-+		echof("Temp boot rom (%d) is bad.\r\n", romToBoot);
- 		// make sure rtc temp boot mode doesn't persist
- 		rtc.next_mode = MODE_STANDARD;
- 		rtc.chksum = calc_chksum((uint8_t*)&rtc, (uint8_t*)&rtc.chksum);
-@@ -504,7 +532,7 @@ uint32_t NOINLINE find_image(void) {
- 
- 	// check we have a good rom
- 	while (loadAddr == 0) {
--		ets_printf("Rom %d at %x is bad.\r\n", romToBoot, romconf->roms[romToBoot]);
-+		echof("Rom %d at %x is bad.\r\n", romToBoot, romconf->roms[romToBoot]);
- 		// for normal mode try each previous rom
- 		// until we find a good one or run out
- 		updateConfig = 1;
-@@ -512,7 +540,7 @@ uint32_t NOINLINE find_image(void) {
- 		if (romToBoot < 0) romToBoot = romconf->count - 1;
- 		if (romToBoot == romconf->current_rom) {
- 			// tried them all and all are bad!
--			ets_printf("No good rom available.\r\n");
-+			echof("No good rom available.\r\n");
- 			return 0;
- 		}
- 		loadAddr = check_image(romconf->roms[romToBoot]);
-@@ -543,7 +571,7 @@ uint32_t NOINLINE find_image(void) {
- 	system_rtc_mem(RBOOT_RTC_ADDR, &rtc, sizeof(rboot_rtc_data), RBOOT_RTC_WRITE);
- #endif
- 
--	ets_printf("Booting rom %d at %x, load addr %x.\r\n", romToBoot, romconf->roms[romToBoot], loadAddr);
-+	echof("Booting rom %d at %x, load addr %x.\r\n", romToBoot, romconf->roms[romToBoot], loadAddr);
- 	// copy the loader to top of iram
- 	ets_memcpy((void*)_text_addr, _text_data, _text_len);
- 	// return address to load from
-diff --git a/appcode/rboot-api.c b/appcode/rboot-api.c
-index eb4d028..bf276a7 100644
---- a/appcode/rboot-api.c
-+++ b/appcode/rboot-api.c
-@@ -9,7 +9,7 @@
- #include <string.h>
- // c_types.h needed for spi_flash.h
- #include <c_types.h>
--#include <spi_flash.h>
-+#include <esp_spi_flash.h>
- 
- #include "rboot-api.h"
- 
-@@ -33,7 +33,7 @@ static uint8_t calc_chksum(uint8_t *start, uint8_t *end) {
- // get the rboot config
- rboot_config ICACHE_FLASH_ATTR rboot_get_config(void) {
- 	rboot_config conf;
--	spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)&conf, sizeof(rboot_config));
-+	flashmem_read(&conf, BOOT_CONFIG_SECTOR * SECTOR_SIZE, sizeof(rboot_config));
- 	return conf;
- }
- 
-@@ -43,7 +43,7 @@ rboot_config ICACHE_FLASH_ATTR rboot_get_config(void) {
- // updates checksum automatically (if enabled)
- bool ICACHE_FLASH_ATTR rboot_set_config(rboot_config *conf) {
- 	uint8_t *buffer;
--	buffer = (uint8_t*)pvPortMalloc(SECTOR_SIZE, 0, 0);
-+	buffer = (uint8_t*)os_malloc(SECTOR_SIZE);
- 	if (!buffer) {
- 		//os_printf("No ram!\r\n");
- 		return false;
-@@ -53,12 +53,12 @@ bool ICACHE_FLASH_ATTR rboot_set_config(rboot_config *conf) {
- 	conf->chksum = calc_chksum((uint8_t*)conf, (uint8_t*)&conf->chksum);
- #endif
- 	
--	spi_flash_read(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)((void*)buffer), SECTOR_SIZE);
-+	flashmem_read(buffer, BOOT_CONFIG_SECTOR * SECTOR_SIZE, SECTOR_SIZE);
- 	memcpy(buffer, conf, sizeof(rboot_config));
--	spi_flash_erase_sector(BOOT_CONFIG_SECTOR);
--	spi_flash_write(BOOT_CONFIG_SECTOR * SECTOR_SIZE, (uint32_t*)((void*)buffer), SECTOR_SIZE);
-+	flashmem_erase_sector(BOOT_CONFIG_SECTOR);
-+	flashmem_write(buffer, BOOT_CONFIG_SECTOR * SECTOR_SIZE, SECTOR_SIZE);
- 	
--	vPortFree(buffer, 0, 0);
-+	os_free(buffer);
- 	return true;
- }
- 
-@@ -103,7 +103,7 @@ bool ICACHE_FLASH_ATTR rboot_write_end(rboot_write_status *status) {
- 
- // function to do the actual writing to flash
- // call repeatedly with more data (max len per write is the flash sector size (4k))
--bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, uint8_t *data, uint16_t len) {
-+bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, const uint8_t *data, uint16_t len) {
- 	
- 	bool ret = false;
- 	uint8_t *buffer;
-@@ -114,7 +114,7 @@ bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, uint8_t *da
- 	}
- 	
- 	// get a buffer
--	buffer = (uint8_t *)pvPortMalloc(len + status->extra_count, 0, 0);
-+	buffer = (uint8_t *)os_malloc(len + status->extra_count);
- 	if (!buffer) {
- 		//os_printf("No ram!\r\n");
- 		return false;
-@@ -141,18 +141,18 @@ bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, uint8_t *da
- 		lastsect = ((status->start_addr + len) - 1) / SECTOR_SIZE;
- 		while (lastsect > status->last_sector_erased) {
- 			status->last_sector_erased++;
--			spi_flash_erase_sector(status->last_sector_erased);
-+			flashmem_erase_sector(status->last_sector_erased);
- 		}
- 
- 		// write current chunk
- 		//os_printf("write addr: 0x%08x, len: 0x%04x\r\n", status->start_addr, len);
--		if (spi_flash_write(status->start_addr, (uint32_t *)((void*)buffer), len) == SPI_FLASH_RESULT_OK) {
-+		if (flashmem_write(buffer, status->start_addr, len) == len) {
- 			ret = true;
- 			status->start_addr += len;
- 		}
- 	//}
- 
--	vPortFree(buffer, 0, 0);
-+	os_free(buffer);
- 	return ret;
- }
- 
-diff --git a/appcode/rboot-api.h b/appcode/rboot-api.h
-index a98c209..ec4f0e5 100644
---- a/appcode/rboot-api.h
-+++ b/appcode/rboot-api.h
-@@ -93,7 +93,7 @@ bool ICACHE_FLASH_ATTR rboot_write_end(rboot_write_status *status);
-  *  of OTA data is received over the network.
-  *  @note   Call rboot_write_init before calling this function to get the rboot_write_status structure
- */
--bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, uint8_t *data, uint16_t len);
-+bool ICACHE_FLASH_ATTR rboot_write_flash(rboot_write_status *status, const uint8_t *data, uint16_t len);
- 
- #ifdef BOOT_RTC_ENABLED
- /** @brief  Get rBoot status/control data from RTC data area
-
diff --git a/Sming/Components/rboot/.patches/rboot/README.rst b/Sming/Components/rboot/.patches/rboot/README.rst
deleted file mode 100644
index e365cdb1f0..0000000000
--- a/Sming/Components/rboot/.patches/rboot/README.rst
+++ /dev/null
@@ -1,349 +0,0 @@
-==================================================
-rBoot - An open source boot loader for the ESP8266
-==================================================
-
-by Richard A Burton, richardaburton@gmail.com
-http://richard.burtons.org/
-
-rBoot is designed to be a flexible open source boot loader, a
-replacement for the binary blob supplied with the SDK. It has the
-following advantages over the Espressif loader:
-
--  Open source (written in C).
--  Supports up to 256 roms.
--  Roms can be variable size.
--  Able to test multiple roms to find a valid backup (without
-   resetting).
--  Flash layout can be changed on the fly (with care and appropriately
-   linked rom images).
--  GPIO support for rom selection.
--  Wastes no stack space (SDK boot loader uses 144 bytes).
--  Documented config structure to allow easy editing from user code.
--  Can validate .irom0.text section with checksum.
--  Temporary next-boot rom selection.
-
-Limitations
-===========
-
-The ESP8266 can only map 8Mbits (1MB) of flash to memory, but which
-8Mbits to map is selectable. This allows individual roms to be up to 1MB
-in size, so long as they do not straddle an 8Mbit boundary on the flash.
-This means you could have four 1MB roms or 8 512KB roms on a 32Mbit
-flash (such as on the ESP-12), or a combination. Note, however, that you
-could not have, for example, a 512KB rom followed immediately by a 1MB
-rom because the 2nd rom would then straddle an 8MBit boundary. By
-default support for using more than the first 8Mbit of the flash is
-disabled, because it requires several steps to get it working. See below
-for instructions.
-
-Building
-========
-
-A Makefile is included, which should work with the gcc xtensa cross
-compiler. There are two source files, the first is compiled and included
-as data in the second. When run this code is copied to memory and
-executed (there is a good reason for this, see my blog for an
-explanation). The make file will handle this for you, but you’ll need my
-esptool2 (see github).
-
-To use the Makefile set ``SDK_BASE`` to point to the root of the
-Espressif SDK and either set ``XTENSA_BINDIR`` to the gcc xtensa bin
-directory or include it in your ``PATH``. These can be set as
-environment variables or by editing the Makefile.
-
-Two small assembler stub functions allow the bootloader to launch the
-user code without reserving any space on the stack (while the SDK boot
-loader uses 144 bytes). This compiles fine with GCC, but if you use
-another compiler and it will not compile/work for you then uncomment the
-``#define BOOT_NO_ASM`` in ``rboot.h`` to use a C version of these
-functions (this uses 32 bytes).
-
-Tested with SDK v2.2 and GCC v4.8.5.
-
-Installation
-============
-
-Simply write rboot.bin to the first sector of the flash. Remember to set
-your flash size correctly with your chosen flash tool (e.g. for
-esptool.py use the ``-fs`` option). When run rBoot will create it’s own
-config at the start of sector two for a simple two rom system. You can
-can then write your two roms to flash addresses ``0x2000`` and (half
-chip size + ``0x2000``). E.g. for 8Mbit flash:
-``esptool.py write_flash -fs 8m 0x0000 rboot.bin 0x2000 user1.bin 0x82000 user2.bin``
-
-Note: your device may need other options specified. E.g. The nodemcu
-devkit v1.0 (commonly, but incorrectly, sold as v2) also needs the
-``-fm dio`` option.
-
-For more interesting rom layouts you’ll need to write an rBoot config
-sector manually, see next step.
-
-The two testload bin files can be flashed in place of normal user roms
-for testing rBoot. You do not need these for normal use.
-
-rBoot Config
-============
-
-::
-
-   typedef struct {
-       uint8_t magic;           // our magic
-       uint8_t version;         // config struct version
-       uint8_t mode;            // boot loader mode
-       uint8_t current_rom;     // currently selected rom
-       uint8_t gpio_rom;        // rom to use for gpio boot
-       uint8_t count;           // number of roms in use
-       uint8_t unused[2];       // padding
-       uint32_t roms[MAX_ROMS]; // flash addresses of the roms
-   #ifdef BOOT_CONFIG_CHKSUM
-       uint8_t chksum;          // boot config chksum
-   #endif
-   } rboot_config;
-
-Write a config structure as above to address ``0x1000`` on the flash. If
-you want more than 4 roms (default) just increase MAX_ROMS when you
-compile rBoot. Think about how you intend to layout your flash before
-you start! Rom addresses must be sector aligned i.e start on a multiple
-of 4096.
-
--  ``magic`` should have value ``0xe1`` (defined as
-   ``BOOT_CONFIG_MAGIC``).
--  ``version`` is used in case the config structure changes after
-   deployment. It is defined as ``0x01`` (``BOOT_CONFIG_VERSION``). I
-   don’t intend to increase this, but you should if you choose to
-   reflash the bootloader after deployment and the config structure has
-   changed.
--  ``mode`` can be ``0x00`` (``MODE_STANDARD``) or ``0x01``
-   (``MODE_GPIO_ROM``). See below for an explanation of
-   ``MODE_GPIO_ROM``. There is also an optional extra mode flag ``0x04``
-   (``MODE_GPIO_ERASES_SDKCONFIG``), see below for details.
--  ``current_rom`` is the rom to boot, numbered ``0`` to ``count-1``.
--  ``gpio_rom`` is the rom to boot when the GPIO is triggered at boot.
--  ``count`` is the number of roms available (may be less than
-   ``MAX_ROMS``, but not more).
--  ``unused[2]`` is padding so the ``uint32_t`` rom addresses are 4
-   bytes aligned.
--  ``roms`` is the array of flash address for the roms. The default
-   generated config will contain two entries: ``0x00002000`` and
-   ``0x00082000``.
--  ``chksum`` (if enabled, not by deafult) should be the xor of ``0xef``
-   followed by each of the bytes of the config structure up to (but
-   obviously not including) the chksum byte itself.
-
-Default config
-==============
-
-A default config sector will be created on boot if one does not exists,
-or if an existing config is corrupted, and the default rom will be set
-to rom 0. If you want to have a very customised config for which the
-default would not be suitable, you can override the implementation in
-the ``rboot.h`` header file. See the comments and example code in
-``rboot.h`` for more information.
-
-GPIO boot mode
-==============
-
-.. envvar:: RBOOT_GPIO_ENABLED
-
-If rBoot is compiled with ``BOOT_GPIO_ENABLED`` set in ``rboot.h`` (or
-``RBOOT_GPIO_ENABLED`` set in the Makefile), then GPIO boot
-functionality will be included in the rBoot binary. The feature can then
-be enabled by setting the rboot_config ``mode`` field to
-``MODE_GPIO_ROM``. You must also set ``gpio_rom`` in the config to
-indicate which rom to boot when the GPIO is activated at boot.
-
-If the GPIO input pin reads high at boot then rBoot will start the
-currently selected normal or temp rom (as appropriate). However if the
-GPIO is pulled low then the rom indicated in config option ``gpio_rom``
-is started instead.
-
-The default GPIO is 16, but this can be overriden in the Makefile
-(``RBOOT_GPIO_NUMBER``) or ``rboot.h`` (``BOOT_GPIO_NUM``). If GPIOs
-other than 16 are used, the internal pullup resistor is enabled before
-the pin is read and disabled immediately afterwards. For pins that
-default on reset to configuration other than GPIO input, the pin mode is
-changed to input when reading but changed back before rboot continues.
-
-After a GPIO boot the ``current_rom`` field will be updated in the
-config, so the GPIO booted rom should change this again if required.
-
-GPIO boot skip mode
-===================
-
-.. envvar:: RBOOT_GPIO_SKIP_ENABLED
-
-If rBoot is compiled with ``BOOT_GPIO_SKIP_ENABLED`` set in ``rboot.h``
-(or ``RBOOT_GPIO_SKIP_ENABLED`` set in the Makefile), then a GPIO can be
-used to skip to the next rom at boot. The feature must then be enabled
-by setting the rboot_config ‘mode’ field to ``MODE_GPIO_SKIP``. This
-means you do not need to have a dedicated GPIO boot rom. If you have a
-rom that is technically good (valid checksum, etc.) but has operational
-problems, e.g. wifi doesn’t work or it crashes on boot, rBoot will not
-be able to detect that and switch rom automatically. In this scenario
-rebooting the device while pulling the GPIO low will force rBoot to skip
-this rom and try the next one instead. In a simple two rom setup this
-simply toggles booting of the other rom.
-
-``RBOOT_GPIO_SKIP_ENABLED`` and ``RBOOT_GPIO_ENABLED`` cannot be used at
-the same time. ``BOOT_GPIO_NUM`` is used to select the GPIO pin, as with
-``RBOOT_GPIO_ENABLED``.
-
-Erasing SDK configuration on GPIO boot (rom or skip mode)
-=========================================================
-
-If you set the ``MODE_GPIO_ERASES_SDKCONFIG`` flag in the configuration
-like this: ``conf.mode = MODE_GPIO_ROM|MODE_GPIO_ERASES_SDKCONFIG``;
-then a GPIO boot will also the erase the Espressif SDK persistent
-settings store in the final 16KB of flash. This includes removing
-calibration constants, saved SSIDs, etc.
-
-Note that ``MODE_GPIO_ERASES_SDKCONFIG`` is a flag, so it has to be set
-as well as ``MODE_GPIO_ROM`` to take effect.
-
-Linking user code
-=================
-
-Each rom will need to be linked with an appropriate linker file,
-specifying where it will reside on the flash. If you are only flashing
-one rom to multiple places on the flash it must be linked multiple times
-to produce the set of rom images. This is the same as with the SDK
-loader.
-
-Because there are endless possibilities for layout with this loader I
-don’t supply sample linker files. Instead I’ll tell you how to make
-them.
-
-For each rom slot on the flash take a copy of the ``eagle.app.v6.ld``
-linker script from the sdk. You then need to modify just one line in it
-for each rom:
-``irom0_0_seg :                         org = 0x40240000, len = 0x3C000``
-
-Change the org address to be ``0x40200000`` (base memory mapped location
-of the flash) + flash address + ``0x10`` (offset of data after the
-header). The logical place for your first rom is the third sector,
-address ``0x2000``. ``0x40200000 + 0x2000 + 0x10 = 0x40202010`` If you
-use the default generated config the loader will expect to find the
-second rom at flash address half-chip-size + ``0x2000``
-(e.g. ``0x82000`` on an 8MBit flash) so the ``irom0_0_seg`` should be:
-``0x40200000 + 0x82000 + 0x10 = 0x40282010`` Due to the limitation of
-mapped flash (max 8MBit) if you use a larger chip and do not have big
-flash support enabled the second rom in the default config will still be
-placed at ``0x082000``, not truly half-chip-size + ``0x2000``. Ideally
-you should also adjust the len to help detect over sized sections at
-link time, but more important is the overall size of the rom which you
-need to ensure fits in the space you have allocated for it in your flash
-layout plan.
-
-Then simply compile and link as you would normally for OTA updates with
-the SDK boot loader, except using the linker scripts you’ve just
-prepared rather than the ones supplied with the SDK. Remember when
-building roms to create them as ‘new’ type roms (for use with SDK boot
-loader v1.2+). Or if using my esptool2 use the ``-boot2`` option. Note:
-the test loads included with rBoot are built with ``-boot0`` because
-they do not contain a ``.irom0.text`` section (and so the value of
-``irom0_0_seg`` in the linker file is irrelevant to them) but ‘normal’
-user apps always do.
-
-irom checksum
-=============
-
-The SDK boot loader checksum only covers sections loaded into ram (data
-and some code). Most of the SDK and user code remains on the flash and
-that is not included in the checksum. This means you could attempt to
-boot a corrupt rom and, because it looks ok to the boot loader, there
-will be no attempt to switch to a backup rom. rBoot improves on this by
-allowing the ``.irom0.text`` section to be included in the checksum. To
-enable this uncomment ``#define BOOT_IROM_CHKSUM`` in ``rboot.h`` and
-build your roms with esptool2 using the ``-iromchksum`` option.
-
-.. _big_flash_support:
-
-Big flash support
-=================
-
-This only needs to be enabled if you wish to be able to memory map more
-than the first 8MBit of the flash. Note you can still only map 8Mbit at
-a time. Use this if you want to have multiple 1MB roms, or more smaller
-roms than will fit in 8Mbits. If you have a large flash but only need,
-for example, two 512KB roms you do not need to enable this mode.
-
-Support in rBoot is enabled by uncommenting the
-``#define BOOT_BIG_FLASH`` in ``rboot.h``.
-
-Thinking about your linker files is either simpler or more complicated,
-depending on your usage of the flash. If you intend to use multiple 1MB
-roms you will only need one linker file and you only need to link once
-for OTA updates. Although when you perform an OTA update the rom will be
-written to a different position on the flash, each 8Mbit of flash is
-mapped (separately) to ``0x40200000``. So when any given rom is run the
-code will appear at the same place in memory regardless of where it is
-on the flash. Your base address for the linker would be ``0x40202010``.
-(Actually all but the first rom could base at ``0x40200010`` (because
-they don’t need to leave space for rBoot and config) but then you’re
-just making it more complicated again!)
-
-If you wanted eight 512KB roms you would need two linker files - one for
-the first half of any given 8Mbits of flash and another for the second
-half. Just remember you are really laying out within a single 8MBit
-area, which can then be replicated multiple times on the flash.
-
-Now the clever bit - rBoot needs to hijack the memory mapping code to
-select which 8Mbits gets mapped. There is no API for this, but we can
-override the SDK function. First we need to slightly modify the SDK
-library ``libmain.a``, like so:
-
-::
-
-   xtensa-lx106-elf-objcopy -W Cache_Read_Enable_New libmain.a libmain2.a
-
-This produces a version of libmain with a ‘weakened’
-``Cache_Read_Enable_New`` function, which we can then override with our
-own. Modify your Makefile to link against the library ``main2`` instead
-of ``main``.
-
-Next add ``rboot-bigflash.c`` (from the ``appcode`` directory) &
-``rboot.h`` to your project - this adds the replacement
-``Cache_Read_Enable_New`` to your code.
-
-Getting gcc to apply the override correctly can be slightly tricky (I’m
-not sure why, it shouldn’t be). One option is to add
-``-u Cache_Read_Enable_New`` to your ``LD_FLAGS`` and change the order
-of objects on the LD command so your ``objects/.a`` file is before the
-libraries. Another way that seems easier was to
-``#include rboot-bigflash.c`` into the main .c file, rather than
-compiling it to a separate object file. I can’t make any sense of that,
-but I suggest you uncomment the message in the ``Cache_Read_Enable_New``
-function when you first build with it, to make sure you are getting your
-version into the rom.
-
-Now when rBoot starts your rom, the SDK code linked in it that normally
-performs the memory mapping will delegate part of that task to rBoot
-code (linked in your rom, not in rBoot itself) to choose which part of
-the flash to map.
-
-Temporary boot option and rBoot<–>app communication
-===================================================
-
-.. envvar:: RBOOT_RTC_ENABLED
-
-To enable communication between rBoot and your app you should enable the
-``BOOT_RTC_ENABLED`` option in ``rboot.h``. rBoot will then use the RTC
-data area to pass a structure with boot information which can be read by
-the app. This will allow the app to determine the boot mode (normal,
-temporary or GPIO) and the booted rom (even if it is a tempoary boot).
-Your app can also update this structure to communicate with rBoot when
-the device is next rebooted, e.g. to instruct it to temporarily boot a
-different rom to the one saved in the config. See the api documentation
-and/or the rBoot sample project for more details. Note: the message
-“don’t use rtc mem data”, commonly seen on startup, comes from the sdk
-and is not related to this rBoot feature.
-
-Integration into other frameworks
-=================================
-
-If you wish to integrate rBoot into a development framework (e.g. Sming)
-you can set the define ``RBOOT_INTEGRATION`` and at compile time the
-file ``rboot-integration.h`` will be included into the source. This
-should allow you to set some platform specific options without having to
-modify the source of rBoot which makes it easier to integrate and
-maintain.
diff --git a/Sming/Components/rboot/README.rst b/Sming/Components/rboot/README.rst
index 50cd239c2a..95ddeb0203 100644
--- a/Sming/Components/rboot/README.rst
+++ b/Sming/Components/rboot/README.rst
@@ -10,10 +10,24 @@ pre-configured flash memory addresses, called "slots". Sming supports up to thre
 .. note::
 
    With Sming 4.3 partitions are used to manage flash storage.
-   A "slot" refers to a specific application partition, namely ``rom0``, ``rom1`` or ``rom2``.
+   A "slot" refers to a specific application partition, typically ``rom0``, ``rom1`` or ``rom2``.
 
    The location or size of these partitions is determined by the :ref:`hardware_config`.
 
+   The bootloader has been modified to use the partition table as reference, identifying slots
+   by the partition sub-type.
+
+   Where systems are to be updated Over the Air (OTA) at least two application partitions are required.
+   The bootloader identifies these by their partition subtype: ``slot #0`` -> ``App/Ota_0``, ``slot #1`` -> ``App/Ota_1``, etc.
+
+   Fixed applications without OTA capability use a single application image.
+   This must be the ``App/Factory`` partition type, and corresponds to ``slot #0``.
+
+   At startup, the bootloader will use the partition table to locate the application image.
+   It will also ensure that the ROM slot information in the boot configuration is consistent,
+   and update it if necessary.
+
+
 .. attention::
 
    Make sure that your slots do not extend beyond a 1MB boundary and 
diff --git a/Sming/Components/rboot/component.mk b/Sming/Components/rboot/component.mk
index 814296d7d5..96f9441e53 100644
--- a/Sming/Components/rboot/component.mk
+++ b/Sming/Components/rboot/component.mk
@@ -125,7 +125,11 @@ endif
 
 COMPONENT_CXXFLAGS += \
 		-DRBOOT_ROM0_ADDR=$(RBOOT_ROM0_ADDR) \
-		-DRBOOT_ROM1_ADDR=$(RBOOT_ROM1_ADDR)
+		-DRBOOT_ROM1_ADDR=$(RBOOT_ROM1_ADDR) \
+		-DPARTITION_TABLE_OFFSET=$(PARTITION_TABLE_OFFSET)
+
+COMPONENT_CFLAGS += \
+		-DPARTITION_TABLE_OFFSET=$(PARTITION_TABLE_OFFSET)
 
 ifdef RBOOT_EMULATION
 FLASH_BOOT_CHUNKS		= 0x00000=$(BLANK_BIN)
@@ -136,7 +140,7 @@ export RBOOT_ROM1_ADDR
 RBOOT_BIN				:= $(FW_BASE)/rboot.bin
 CUSTOM_TARGETS			+= $(RBOOT_BIN)
 $(RBOOT_BIN):
-	$(Q) $(MAKE) -C $(RBOOT_DIR)/rboot $(RBOOT_CFLAGS)
+	$(Q) $(MAKE) -C $(RBOOT_DIR)/rboot PARTITION_TABLE_OFFSET=$(PARTITION_TABLE_OFFSET) $(RBOOT_CFLAGS)
 
 EXTRA_LDFLAGS			:= -u Cache_Read_Enable_New
 
diff --git a/Sming/Components/rboot/include/Data/Stream/RbootOutputStream.h b/Sming/Components/rboot/include/Data/Stream/RbootOutputStream.h
index 30c8b85db8..1873d01885 100644
--- a/Sming/Components/rboot/include/Data/Stream/RbootOutputStream.h
+++ b/Sming/Components/rboot/include/Data/Stream/RbootOutputStream.h
@@ -17,6 +17,7 @@
 
 #include <Data/Stream/ReadWriteStream.h>
 #include <rboot-api.h>
+#include <Storage/Partition.h>
 
 /**
  * @brief Write-only stream type used during rBoot firmware updates
@@ -25,13 +26,23 @@ class RbootOutputStream : public ReadWriteStream
 {
 public:
 	/**
+	 * @brief Construct a stream using raw flash address/size
 	 * @param startAddress the start address on the storage media
 	 * @param maxLength the maximum allowed length of the rom. Use 0 if unlimited.
+	 * @note This should be avoided, use partition where possible
 	 */
 	RbootOutputStream(uint32_t startAddress, size_t maxLength = 0) : startAddress(startAddress), maxLength(maxLength)
 	{
 	}
 
+	/**
+	 * @brief Construct a stream for the given partition
+	 * @param partition
+	 */
+	RbootOutputStream(Storage::Partition partition) : startAddress(partition.address()), maxLength(partition.size())
+	{
+	}
+
 	virtual ~RbootOutputStream()
 	{
 		close();
@@ -77,11 +88,11 @@ class RbootOutputStream : public ReadWriteStream
 	}
 
 protected:
-	bool initialized = false;
-	rboot_write_status rBootWriteStatus = {};
-	size_t written = 0;		   // << the number of written bytes
-	uint32_t startAddress = 0; // << the start address on the storage
-	size_t maxLength = 0;	  // << maximum allowed length
+	bool initialized{false};
+	rboot_write_status rBootWriteStatus{};
+	size_t written{0};		  // << the number of written bytes
+	uint32_t startAddress{0}; // << the start address on the storage
+	size_t maxLength{0};	  // << maximum allowed length
 
 protected:
 	virtual bool init();
diff --git a/Sming/Components/rboot/include/Network/RbootHttpUpdater.h b/Sming/Components/rboot/include/Network/RbootHttpUpdater.h
index c7ed16a43c..f8328ed764 100644
--- a/Sming/Components/rboot/include/Network/RbootHttpUpdater.h
+++ b/Sming/Components/rboot/include/Network/RbootHttpUpdater.h
@@ -18,32 +18,101 @@
 #include <Network/HttpClient.h>
 #include "../Data/Stream/RbootOutputStream.h"
 
-#define NO_ROM_SWITCH 0xff
+/**
+ * @brief Magic value for ROM slot indicating slot won't change after successful OTA
+ */
+constexpr uint8_t NO_ROM_SWITCH{0xff};
 
 class RbootHttpUpdater;
 
-typedef Delegate<void(RbootHttpUpdater& client, bool result)> OtaUpdateDelegate;
-
-struct RbootHttpUpdaterItem {
-	String url;
-	uint32_t targetOffset;
-	size_t size;						 // << max allowed size
-	RbootOutputStream* stream = nullptr; // (optional) output stream to use.
-};
+using OtaUpdateDelegate = Delegate<void(RbootHttpUpdater& client, bool result)>;
 
 class RbootHttpUpdater : protected HttpClient
 {
 public:
-	virtual ~RbootHttpUpdater()
+	struct Item {
+		String url;
+		uint32_t targetOffset;
+		size_t size;						// << max allowed size
+		RbootOutputStream* stream{nullptr}; // (optional) output stream to use.
+
+		Item(String url, uint32_t targetOffset, size_t size, RbootOutputStream* stream)
+			: url(url), targetOffset(targetOffset), size(size), stream(stream)
+		{
+		}
+
+		~Item()
+		{
+			delete stream;
+		}
+
+		RbootOutputStream* getStream()
+		{
+			if(stream == nullptr) {
+				stream = new RbootOutputStream(targetOffset, size);
+			}
+			return stream;
+		}
+	};
+
+	class ItemList : public Vector<Item>
 	{
-		cleanup();
+	public:
+		bool addNew(Item* it)
+		{
+			if(addElement(it)) {
+				return true;
+			}
+			delete it;
+			return false;
+		}
+	};
+
+	/**
+	 * @brief Add an item to update
+	 * @param offset
+	 * @param firmwareFileUrl
+	 * @param maxSize
+	 * @retval bool
+	 * @note Use the `Partition` overload where possible
+	 */
+	bool addItem(uint32_t offset, const String& firmwareFileUrl, size_t maxSize = 0)
+	{
+		return items.addNew(new Item{firmwareFileUrl, offset, maxSize, nullptr});
+	}
+
+	/**
+	 * @brief Add an item to update
+	 * @param firmwareFileUrl
+	 * @param partition Target partition to write
+	 * @retval bool
+	 */
+	bool addItem(const String& firmwareFileUrl, Storage::Partition partition)
+	{
+		return addItem(partition.address(), firmwareFileUrl, partition.size());
 	}
 
-	bool addItem(uint32_t offset, const String& firmwareFileUrl, size_t maxSize = 0);
-	bool addItem(const String& firmwareFileUrl, RbootOutputStream* stream = nullptr);
+	/**
+	 * @brief Add an item to update use a pre-constructed stream
+	 * @param firmwareFileUrl
+	 * @param stream
+	 * @retval bool
+	 */
+	bool addItem(const String& firmwareFileUrl, RbootOutputStream* stream)
+	{
+		if(stream == nullptr) {
+			return false;
+		}
+
+		return items.addNew(new Item{firmwareFileUrl, stream->getStartAddress(), stream->getMaxLength(), stream});
+	}
 
 	void start();
 
+	/**
+	 * @brief On completion, switch to the given ROM slot
+	 * @param romSlot specify NO_ROM_SWITCH (the default) to cancel any previously set switch
+	 */
 	void switchToRom(uint8_t romSlot)
 	{
 		this->romSlot = romSlot;
@@ -59,11 +128,13 @@ class RbootHttpUpdater : protected HttpClient
 		this->updateDelegate = reqUpdateDelegate;
 	}
 
-	/* Sets the base request that can be used to pass
-	 * - default request parameters, like request headers...
-	 * - default SSL options
-	 * - default SSL fingeprints
-	 * - default SSL client certificates
+	/**
+	 *  @brief Sets the base request that can be used to pass
+	 * 
+	 * 		- default request parameters, like request headers...
+	 * 		- default SSL options
+	 * 		- default SSL fingeprints
+	 * 		- default SSL client certificates
 	 *
 	 * @param request
 	 */
@@ -72,10 +143,21 @@ class RbootHttpUpdater : protected HttpClient
 		baseRequest = request;
 	}
 
-	// Allow reading items
-	RbootHttpUpdaterItem getItem(unsigned int index)
+	/**
+	 * @brief Allow reading items
+	 * @deprecated Access list directly using `getItems()`
+	 */
+	const Item& getItem(unsigned int index) const SMING_DEPRECATED
+	{
+		return items[index];
+	}
+
+	/**
+	 * @brief Allow read access to item list
+	 */
+	const ItemList& getItems() const
 	{
-		return items.elementAt(index);
+		return items;
 	}
 
 protected:
@@ -86,24 +168,16 @@ class RbootHttpUpdater : protected HttpClient
 	virtual int updateComplete(HttpConnection& client, bool success);
 
 protected:
-	Vector<RbootHttpUpdaterItem> items;
-	int currentItem = 0;
-	rboot_write_status rbootWriteStatus;
-	uint8_t romSlot = NO_ROM_SWITCH;
-	OtaUpdateDelegate updateDelegate = nullptr;
-
-	HttpRequest* baseRequest = nullptr;
-
-private:
-	void cleanup()
-	{
-		for(unsigned i = 0; i < items.count(); i++) {
-			delete items[i].stream;
-			items[i].stream = nullptr;
-		}
-		items.clear();
-	}
+	ItemList items;
+	OtaUpdateDelegate updateDelegate;
+	HttpRequest* baseRequest{nullptr};
+	uint8_t romSlot{NO_ROM_SWITCH};
+	uint8_t currentItem{0};
+	rboot_write_status rbootWriteStatus{};
 };
 
 /** @deprecated Use `RbootHttpUpdater` */
 typedef RbootHttpUpdater rBootHttpUpdate SMING_DEPRECATED;
+
+/** @deprecated Use 'auto' in expressions or `RbootHttpUpdater::Item` */
+typedef RbootHttpUpdater::Item RBootHttpUpdaterItem SMING_DEPRECATED;
diff --git a/Sming/Components/rboot/rboot b/Sming/Components/rboot/rboot
index 614f33685d..4ad3ba2a8f 160000
--- a/Sming/Components/rboot/rboot
+++ b/Sming/Components/rboot/rboot
@@ -1 +1 @@
-Subproject commit 614f33685d0dd990fc4202f2409b0d2365eeaef3
+Subproject commit 4ad3ba2a8f6d48d8ab90e0e7c67d42cac49f4da3
diff --git a/Sming/Components/rboot/src/Arch/Host/rboot.cpp b/Sming/Components/rboot/src/Arch/Host/rboot.cpp
index 96c5719d88..3eec026938 100644
--- a/Sming/Components/rboot/src/Arch/Host/rboot.cpp
+++ b/Sming/Components/rboot/src/Arch/Host/rboot.cpp
@@ -13,6 +13,16 @@
 #include <hostlib/hostmsg.h>
 #include <WString.h>
 
+static uint32_t SPIRead(uint32_t addr, void* outptr, uint32_t len)
+{
+	return (flashmem_read(outptr, addr, len) == len) ? 0 : 1;
+}
+
+#define SPIEraseSector(sector) flashmem_erase_sector(sector)
+#define echof(fmt, ...) host_printf(fmt, ##__VA_ARGS__)
+
+#include <partition.h>
+
 /*
  * Called from emulator startup code after flash has been initialised.
  */
@@ -20,18 +30,14 @@ void host_init_bootloader()
 {
 	rboot_config romconf = rboot_get_config();
 
-	bool init;
-
 	// fresh install or old version?
+	bool init = false;
 	if(romconf.magic != BOOT_CONFIG_MAGIC) {
 		hostmsg("MAGIC mismatch");
 		init = true;
 	} else if(romconf.version != BOOT_CONFIG_VERSION) {
 		hostmsg("VERSION mismatch: %u found, current %u", romconf.version, BOOT_CONFIG_VERSION);
 		init = true;
-	} else {
-		// OK
-		init = false;
 	}
 
 	if(init) {
@@ -39,16 +45,22 @@ void host_init_bootloader()
 		memset(&romconf, 0, sizeof(romconf));
 		romconf.magic = BOOT_CONFIG_MAGIC;
 		romconf.version = BOOT_CONFIG_VERSION;
-		romconf.count = 2;
-		romconf.roms[0] = RBOOT_ROM0_ADDR;
-#ifdef BOOT_ROM1_ADDR
-		romconf.roms[1] = RBOOT_ROM1_ADDR;
-#else
-		size_t flashsize = flashmem_get_size_bytes();
-		romconf.roms[1] = RBOOT_ROM0_ADDR + (flashsize / 2);
-#endif
+	}
+
+	// Read ROM locations from partition table
+	bool config_changed = false;
+	if(scan_partitions(&romconf)) {
+		config_changed = true;
+	}
+
+	if(romconf.count == 0) {
+		hostmsg("ERROR! No App partitions found\r\n");
+		return;
+	}
+
+	if(init || config_changed) {
 		bool ok = rboot_set_config(&romconf);
-		hostmsg("Write default config: %s", ok ? "OK" : "FAIL");
+		hostmsg("Update rBoot config: %s", ok ? "OK" : "FAIL");
 	}
 
 	String addr;
diff --git a/Sming/Components/rboot/src/RbootHttpUpdater.cpp b/Sming/Components/rboot/src/RbootHttpUpdater.cpp
index 07d28bf31d..78ed79e27a 100644
--- a/Sming/Components/rboot/src/RbootHttpUpdater.cpp
+++ b/Sming/Components/rboot/src/RbootHttpUpdater.cpp
@@ -15,36 +15,13 @@
 
 #include <Network/RbootHttpUpdater.h>
 
-bool RbootHttpUpdater::addItem(uint32_t offset, const String& firmwareFileUrl, size_t maxSize)
-{
-	RbootHttpUpdaterItem add;
-	add.targetOffset = offset;
-	add.url = firmwareFileUrl;
-	add.size = maxSize;
-	add.stream = nullptr;
-	return items.add(add);
-}
-
-bool RbootHttpUpdater::addItem(const String& firmwareFileUrl, RbootOutputStream* stream)
-{
-	if(stream == nullptr) {
-		return false;
-	}
-
-	RbootHttpUpdaterItem add;
-	add.targetOffset = stream->getStartAddress();
-	add.url = firmwareFileUrl;
-	add.size = stream->getMaxLength();
-	add.stream = stream;
-
-	return items.add(add);
-}
-
 void RbootHttpUpdater::start()
 {
 	for(unsigned i = 0; i < items.count(); i++) {
-		RbootHttpUpdaterItem& it = items[i];
-		debug_d("Download file:\r\n    (%d) %s -> %X", currentItem, it.url.c_str(), it.targetOffset);
+		auto& it = items[i];
+		debug_d("Download file:\r\n"
+				"    (%u) %s -> %X",
+				currentItem, it.url.c_str(), it.targetOffset);
 
 		HttpRequest* request;
 		if(baseRequest != nullptr) {
@@ -55,12 +32,7 @@ void RbootHttpUpdater::start()
 		}
 
 		request->setMethod(HTTP_GET);
-
-		if(it.stream == nullptr) {
-			it.stream = new RbootOutputStream(it.targetOffset, it.size);
-		}
-
-		request->setResponseStream(it.stream);
+		request->setResponseStream(it.getStream());
 
 		if(i == items.count() - 1) {
 			request->onRequestComplete(RequestCompletedDelegate(&RbootHttpUpdater::updateComplete, this));
@@ -82,8 +54,8 @@ int RbootHttpUpdater::itemComplete(HttpConnection& client, bool success)
 		return -1;
 	}
 
-	RbootHttpUpdaterItem& it = items[currentItem];
-	debug_d("Finished: URL: %s, Offset: %d, Length: %d", it.url.c_str(), it.stream->getStartAddress(),
+	auto& it = items[currentItem];
+	debug_d("Finished: URL: %s, Offset: 0x%X, Length: %u", it.url.c_str(), it.stream->getStartAddress(),
 			it.stream->available());
 
 	it.stream = nullptr; // the actual deletion will happen outside of this class
@@ -94,14 +66,14 @@ int RbootHttpUpdater::itemComplete(HttpConnection& client, bool success)
 
 int RbootHttpUpdater::updateComplete(HttpConnection& client, bool success)
 {
-	auto hasError = itemComplete(client, success);
-	if(hasError) {
+	int hasError = itemComplete(client, success);
+	if(hasError != 0) {
 		return hasError;
 	}
 
 	debug_d("\r\nFirmware download finished!");
 	for(unsigned i = 0; i < items.count(); i++) {
-		debug_d(" - item: %d, addr: %X, url: %s", i, items[i].targetOffset, items[i].url.c_str());
+		debug_d(" - item: %u, addr: 0x%X, url: %s", i, items[i].targetOffset, items[i].url.c_str());
 	}
 
 	if(!success) {
@@ -124,19 +96,19 @@ void RbootHttpUpdater::updateFailed()
 	if(updateDelegate) {
 		updateDelegate(*this, false);
 	}
-	cleanup();
+	items.clear();
 }
 
 void RbootHttpUpdater::applyUpdate()
 {
-	cleanup();
+	items.clear();
 	if(romSlot == NO_ROM_SWITCH) {
 		debug_d("Firmware updated.");
 		return;
 	}
 
 	// set to boot new rom and then reboot
-	debug_d("Firmware updated, rebooting to rom %d...\r\n", romSlot);
+	debug_d("Firmware updated, rebooting to rom %u...\r\n", romSlot);
 	rboot_set_current_rom(romSlot);
 	System.restart();
 }
diff --git a/Sming/Components/spiffs/component.mk b/Sming/Components/spiffs/component.mk
index e82a68d993..77064eb4a3 100644
--- a/Sming/Components/spiffs/component.mk
+++ b/Sming/Components/spiffs/component.mk
@@ -25,22 +25,12 @@ COMPONENT_RELINK_VARS += SPIFFS_OBJ_META_LEN
 SPIFFS_OBJ_META_LEN ?= 16
 COMPONENT_CFLAGS += -DSPIFFS_OBJ_META_LEN=$(SPIFFS_OBJ_META_LEN)
 
-##@Cleaning
-
-.PHONY: spiffs-image-clean
-spiffs-image-clean: ##Remove SPIFFS image file
-	$(info Cleaning $(SPIFF_BIN_OUT))
-	$(Q) rm -f $(SPIFF_BIN_OUT)
-
 ##@Building
 
 # Spiffs image generation tool
 SPIFFSGEN := $(PYTHON) $(COMPONENT_PATH)/spiffsgen.py
 SPIFFSGEN_SMING = $(SPIFFSGEN) --meta-len=$(SPIFFS_OBJ_META_LEN) --block-size=8192
 
-.PHONY: spiffs-image-update
-spiffs-image-update: spiffs-image-clean $(SPIFF_BIN_OUT) ##Rebuild the SPIFFS filesystem image
-
 # Target invoked via partition table
 ifneq (,$(filter spiffsgen,$(MAKECMDGOALS)))
 PART_TARGET := $(PARTITION_$(PART)_FILENAME)
diff --git a/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.cpp b/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.cpp
index c1ac6be9e4..af28c04518 100644
--- a/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.cpp
+++ b/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.cpp
@@ -13,6 +13,7 @@
 #include <esp_spi_flash.h>
 #include <Data/HexString.h>
 #include <FlashString/Array.hpp>
+#include <Storage/SpiFlash.h>
 
 extern "C" uint32 user_rf_cal_sector_set(void);
 
@@ -27,29 +28,13 @@ DECLARE_FSTR_ARRAY(AppFlashRegionOffsets, uint32_t);
 BasicStream::Slot::Slot()
 {
 	// Get parameters of the slot where the firmware image should be stored.
-	const rboot_config bootConfig = rboot_get_config();
-	uint8_t currentSlot = bootConfig.current_rom;
-	index = (currentSlot == 0 ? 1 : 0);
-	address = bootConfig.roms[index];
-	size = 0x100000 - (address & 0xFFFFF);
+	uint8_t currentSlot = rboot_get_current_rom();
+	index = (currentSlot == 0) ? 1 : 0;
 
-	const auto limitSize = [&](uint32_t otherAddress) {
-		if(otherAddress > address) {
-			size = std::min(size, otherAddress - address);
-		}
-	};
-
-	limitSize(flashmem_get_size_bytes());
-	limitSize(user_rf_cal_sector_set() * INTERNAL_FLASH_SECTOR_SIZE);
-	for(uint8_t i = 0; i < bootConfig.count; ++i) {
-		if(i != currentSlot) {
-			limitSize(bootConfig.roms[i]);
-		}
-	}
-
-	for(auto offset : AppFlashRegionOffsets) {
-		limitSize(offset);
-	}
+	// Lookup slot details from partition table
+	auto part = Storage::spiFlash->partitions().findOta(index);
+	address = part.address();
+	size = part.size();
 }
 
 BasicStream::BasicStream()
@@ -83,7 +68,7 @@ bool BasicStream::consume(const uint8_t*& data, size_t& size)
 void BasicStream::setError(Error code)
 {
 	assert(code != Error::None);
-	debug_e("Error: %s", errorToString(code).c_str());
+	debug_e("Error: %s", toString(code).c_str());
 	errorCode = code;
 	state = State::Error;
 }
@@ -225,6 +210,14 @@ size_t BasicStream::write(const uint8_t* data, size_t size)
 
 String BasicStream::errorToString(Error code)
 {
+	return toString(code);
+}
+
+} // namespace OtaUpgrade
+
+String toString(OtaUpgrade::BasicStream::Error code)
+{
+	using Error = OtaUpgrade::BasicStream::Error;
 	switch(code) {
 	case Error::None:
 		return nullptr;
@@ -254,5 +247,3 @@ String BasicStream::errorToString(Error code)
 		return F("<unknown error>");
 	}
 }
-
-} // namespace OtaUpgrade
diff --git a/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.h b/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.h
index 995c57034d..303431caae 100644
--- a/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.h
+++ b/Sming/Libraries/OtaUpgrade/OtaUpgrade/BasicStream.h
@@ -48,7 +48,7 @@ class BasicStream : public ReadWriteStream
 	/**
 	 * @brief Error code values
 	 */
-	enum Error {
+	enum class Error {
 		None,			  ///< No error occured thus far (default value of \c #errorCode if `hasError()` returns false)
 		InvalidFormat,	///< Invalid/unsupported upgrade file format
 		UnsupportedData,  ///< Some content of the upgrade file is not supported by this version of OtaUpgradeStream.
@@ -67,8 +67,9 @@ class BasicStream : public ReadWriteStream
 
 	/** @brief Convert error code to string.
 	 * @see #errorCode
+	 * @deprecated Use `toString()` global function
 	 */
-	static String errorToString(Error code);
+	static String errorToString(Error code) SMING_DEPRECATED;
 
 	/** @brief Process chunk of upgrade file.
 	 * @param data Pointer to chunk of data.
@@ -113,11 +114,12 @@ class BasicStream : public ReadWriteStream
 		uint32_t address;
 		uint32_t size;
 		uint8_t index;
-		bool updated = false;
-	} slot;
+		bool updated{false};
+	};
+	Slot slot;
 
 	// Instead of RbootOutputStream, the rboot write API is used directly because in a future extension the OTA file may contain data for multiple FLASH regions.
-	rboot_write_status rbootWriteStatus = {};
+	rboot_write_status rbootWriteStatus{};
 
 	enum class State {
 		Error,
@@ -128,14 +130,14 @@ class BasicStream : public ReadWriteStream
 		VerifyRoms,
 		RomsComplete,
 	};
-	State state = State::Header;
+	State state{State::Header};
 
 #ifdef ENABLE_OTA_SIGNING
 	using Verifier = SignatureVerifier;
-	static const uint32_t expectedHeaderMagic = OTA_HEADER_MAGIC_SIGNED;
+	static const uint32_t expectedHeaderMagic{OTA_HEADER_MAGIC_SIGNED};
 #else
 	using Verifier = ChecksumVerifier;
-	static const uint32_t expectedHeaderMagic = OTA_HEADER_MAGIC_NOT_SIGNED;
+	static const uint32_t expectedHeaderMagic{OTA_HEADER_MAGIC_NOT_SIGNED};
 #endif
 	Verifier verifier;
 
@@ -144,9 +146,9 @@ class BasicStream : public ReadWriteStream
 
 	Verifier::VerificationData verificationData;
 
-	size_t remainingBytes = 0;
-	uint8_t* destinationPtr = nullptr;
-	uint8_t romIndex = 0;
+	size_t remainingBytes{0};
+	uint8_t* destinationPtr{nullptr};
+	uint8_t romIndex{0};
 
 	void setupChunk(State nextState, size_t size, void* destination = nullptr)
 	{
@@ -183,3 +185,8 @@ class BasicStream : public ReadWriteStream
 };
 
 } // namespace OtaUpgrade
+
+/** @brief Convert error code to string.
+ * @see #errorCode
+ */
+String toString(OtaUpgrade::BasicStream::Error code);
diff --git a/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.cpp b/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.cpp
index 80a88457af..fa1c09a0f8 100644
--- a/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.cpp
+++ b/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.cpp
@@ -46,16 +46,15 @@ size_t EncryptedStream::write(const uint8_t* data, size_t size)
 
 			case Fragment::ChunkSize:
 				remainingBytes = 1 + chunkSizeMinusOne;
-				if(buffer == nullptr || bufferSize < remainingBytes) {
-					free(buffer);
-					buffer = (uint8_t*)malloc(remainingBytes);
-					if(buffer == nullptr) {
+				if(!buffer || bufferSize < remainingBytes) {
+					buffer.reset(new uint8_t[remainingBytes]);
+					if(!buffer) {
 						setError(Error::OutOfMemory);
 						break;
 					}
 					bufferSize = remainingBytes;
 				}
-				fragmentPtr = buffer;
+				fragmentPtr = buffer.get();
 				fragment = Fragment::Chunk;
 				break;
 
@@ -63,8 +62,8 @@ size_t EncryptedStream::write(const uint8_t* data, size_t size)
 				unsigned char tag;
 				size_t chiperTextLength = 1 + chunkSizeMinusOne;
 				unsigned long long messageLength = 0;
-				bool ok = (crypto_secretstream_xchacha20poly1305_pull(&state, buffer, &messageLength, &tag, buffer,
-																	  chiperTextLength, nullptr, 0) == 0);
+				bool ok = (crypto_secretstream_xchacha20poly1305_pull(&state, buffer.get(), &messageLength, &tag,
+																	  buffer.get(), chiperTextLength, nullptr, 0) == 0);
 				if(!ok || messageLength > bufferSize) {
 					setError(Error::DecryptionFailed);
 					break;
@@ -77,7 +76,7 @@ size_t EncryptedStream::write(const uint8_t* data, size_t size)
 					fragment = Fragment::None;
 				}
 
-				BasicStream::write(buffer, static_cast<size_t>(messageLength));
+				BasicStream::write(buffer.get(), size_t(messageLength));
 			} break;
 
 			case Fragment::None:
diff --git a/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.h b/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.h
index 3557c56f82..7b34375a33 100644
--- a/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.h
+++ b/Sming/Libraries/OtaUpgrade/OtaUpgrade/EncryptedStream.h
@@ -28,11 +28,6 @@ class EncryptedStream : public BasicStream
 public:
 	EncryptedStream() = default;
 
-	~EncryptedStream()
-	{
-		free(buffer);
-	}
-
 	/** @brief Process an arbitrarily sized chunk of an encrypted OTA upgrade file.
 	 * @param data Pointer to chunk of data.
 	 * @param size Size of chunk pointed to by \a data in bytes.
@@ -55,12 +50,12 @@ class EncryptedStream : public BasicStream
 		Chunk,
 		None,
 	};
-	Fragment fragment = Fragment::Header;
 
-	size_t remainingBytes = sizeof(header);
-	uint8_t* fragmentPtr = header;
-	uint8_t* buffer = nullptr;
-	size_t bufferSize = 0;
+	Fragment fragment{Fragment::Header};
+	size_t remainingBytes{sizeof header};
+	uint8_t* fragmentPtr{header};
+	std::unique_ptr<uint8_t[]> buffer;
+	size_t bufferSize{0};
 };
 
 } // namespace OtaUpgrade
diff --git a/Sming/component.mk b/Sming/component.mk
index 8f3471a83c..4d3ba173fa 100644
--- a/Sming/component.mk
+++ b/Sming/component.mk
@@ -149,10 +149,9 @@ GLOBAL_CFLAGS		+= -DSTRING_OBJECT_SIZE=$(STRING_OBJECT_SIZE)
 ##@Flashing
 
 .PHONY: flashinit
-flashinit: $(ESPTOOL) $(FLASH_INIT_DATA) | $(FW_BASE) ##Erase your device's flash memory
+flashinit: $(ESPTOOL) | $(FW_BASE) ##Erase your device's flash memory
 	$(info Flash init data default and blank data)
 	$(Q) $(EraseFlash)
-	$(call WriteFlash,$(FLASH_INIT_CHUNKS))
 
 .PHONY: flashboot
 flashboot: $(FLASH_BOOT_LOADER) kill_term ##Write just the Bootloader
diff --git a/Tools/vscode/setup.py b/Tools/vscode/setup.py
index 4e59b0a0ab..8363406a23 100644
--- a/Tools/vscode/setup.py
+++ b/Tools/vscode/setup.py
@@ -32,6 +32,11 @@ def replace(self, path, name, prefix):
                 return '${%s%s}%s' % (prefix, name, s[len(value):])
         return path
 
+    def resolve(self, path):
+        """Convert any embedded environment variables into real paths
+        """
+        return os.path.expandvars(path)
+
     def subst_path(self, path, prefix=''):
         path = self.replace(path, 'SMING_HOME', prefix)
         path = self.replace(path, 'ESP_HOME', prefix)
@@ -113,9 +118,10 @@ def get_property(data, name, default):
     return data[name]
 
 def update_intellisense():
-    dirs = os.environ['COMPONENTS_EXTRA_INCDIR'].split()
-    for i, d in enumerate(dirs):
-        dirs[i] = check_path(env.subst_path(d))
+    dirs = []
+    for d in os.environ['COMPONENTS_EXTRA_INCDIR'].split():
+        if os.path.exists(d):
+            dirs += [check_path(env.subst_path(d))]
 
     propertiesFile = '.vscode/c_cpp_properties.json'
     if os.path.exists(propertiesFile):
@@ -185,11 +191,15 @@ def update_launch():
 def update_workspace():
     filename = 'sming.code-workspace'
     ws = load_json(filename, False)
+    template = load_template('workspace.json')
     if ws is None:
-        template = load_template('workspace.json')
         ws = template.copy()
-        # TODO: Make any required changes to generated
-        save_json(ws, filename)
+    schemas = ws['settings']['json.schemas'] = []
+    # ws['settings']['json.schemas']
+    for schema in template['settings']['json.schemas']:
+        schema['url'] = env.resolve(schema['url'])
+        schemas += [schema]
+    save_json(ws, filename)
 
 def main():
     if not env.SMING_HOME or not env.SMING_ARCH:
diff --git a/Tools/vscode/template/workspace.json b/Tools/vscode/template/workspace.json
index f5d5823978..a79cc17307 100644
--- a/Tools/vscode/template/workspace.json
+++ b/Tools/vscode/template/workspace.json
@@ -15,13 +15,13 @@
                 "fileMatch": [
                     "*.hw"
                 ],
-                "url": "{$SMING_HOME}/Components/Storage/schema.json"
+                "url": "file://${SMING_HOME}/Components/Storage/schema.json"
             },
             {
                 "fileMatch": [
                     "*.fwfs"
                 ],
-                "url": "${SMING_HOME}/Components/IFS/fsbuild/schema.json"
+                "url": "file://${SMING_HOME}/Components/IFS/fsbuild/schema.json"
             }
         ]
     }
diff --git a/docs/source/upgrading/4.2-4.3.rst b/docs/source/upgrading/4.2-4.3.rst
index 69fb0498e4..6698e12634 100644
--- a/docs/source/upgrading/4.2-4.3.rst
+++ b/docs/source/upgrading/4.2-4.3.rst
@@ -17,6 +17,12 @@ the :envvar:`HWCONFIG` setting.
 You can find full details in the :component:`Storage` library.
 See also background on :doc:`/information/flash`.
 
+Removed build targets
+    spiffs-image-update
+        Use the new `buildpart` target instead
+    spiffs-image-clean
+        Use the new `part-clean` target instead
+
 New and updated build targets
     hwconfig
         Displays the current configuration in JSON format
@@ -36,22 +42,18 @@ New and updated build targets
         ``make flashinit`` to write system parameter information. Failure to do this
         was a common problem and should now be a thing of the past.
     flashinit
-        This now just erases the flash memory, and is no longer a pre-requisite for ``make flash``.
-
-        The ESP8266 system parameter information has been moved into registered
-        partitions (phy_init and sys_param) near the beginning of flash memory.
-        It now gets written when running ``make flash``.
-
-        Previously, this information was kept right at the end of the flash memory,
-        so the location would vary depending on the setting of ``SPI_SIZE``.
-        This was a frequent cause of problems as the system would fail to start if this
-        was set incorrectly.
+        This now just erases the flash memory.
+        The ESP8266 initialisation data gets written when running ``make flash``.
     flashmap
         Flash just the partition map
     flashpart
         Flash a single partition, e.g. ``make flashpart PART=spiffs0``
     erasepart
         Erase a partition, e.g. ``make erasepart PART=spiffs0``
+    buildpart
+        Re-builds images associated with partitions, such as SPIFFS or other filesystem images.
+    part-clean
+        Removes any partition images with build information. This is done as part of a normal project `clean`.
 
 Configuration variables
     A number of configuration variables have been removed or made read-only, as these are now
diff --git a/samples/Basic_Storage/basic_storage.hw b/samples/Basic_Storage/basic_storage.hw
index 90da02eb6d..44caa91626 100644
--- a/samples/Basic_Storage/basic_storage.hw
+++ b/samples/Basic_Storage/basic_storage.hw
@@ -2,8 +2,15 @@
 	"name": "Basic Storage sample",
 	"base_config": "spiffs",
 	"devices": {
-		// Override default (conservative) flash settings for maximum performance
+		// If required, override default (conservative) flash settings
 		"spiFlash": {
+			/*
+			 * The default mode (dio) should work with all flash devices.
+			 * Make sure to set the mode according to your flash chip.
+			 * Allowed SPI modes are listed here:
+			 * https://sming.readthedocs.io/en/latest/_inc/Sming/Components/esptool/index.html#envvar-SPI_MODE.
+			*/
+			// "mode": "qio",
 			"speed": 80
 		}
 	},
diff --git a/samples/Basic_rBoot/app/application.cpp b/samples/Basic_rBoot/app/application.cpp
index b8e7397a30..cfb3f85655 100644
--- a/samples/Basic_rBoot/app/application.cpp
+++ b/samples/Basic_rBoot/app/application.cpp
@@ -96,17 +96,16 @@ void OtaUpdate()
 
 void Switch()
 {
-	uint8 before, after;
-	before = rboot_get_current_rom();
-	if(before == 0) {
-		after = 1;
+	uint8_t before = rboot_get_current_rom();
+	uint8_t after = (before == 0) ? 1 : 0;
+
+	Serial.printf(_F("Swapping from rom %u to rom %u.\r\n"), before, after);
+	if(rboot_set_current_rom(after)) {
+		Serial.println(F("Restarting...\r\n"));
+		System.restart();
 	} else {
-		after = 0;
+		Serial.println(F("Switch failed."));
 	}
-	Serial.printf("Swapping from rom %d to rom %d.\r\n", before, after);
-	rboot_set_current_rom(after);
-	Serial.println("Restarting...\r\n");
-	System.restart();
 }
 
 void ShowInfo()
@@ -116,15 +115,15 @@ void ShowInfo()
 	Serial.printf("CPU Frequency: %d MHz\r\n", system_get_cpu_freq());
 	Serial.printf("System Chip ID: %x\r\n", system_get_chip_id());
 	Serial.printf("SPI Flash ID: %x\r\n", Storage::spiFlash->getId());
-	//Serial.printf("SPI Flash Size: %d\r\n", (1 << ((spi_flash_get_id() >> 16) & 0xff)));
+	Serial.printf("SPI Flash Size: %x\r\n", Storage::spiFlash->getSize());
 
 	rboot_config conf;
 	conf = rboot_get_config();
 
 	debugf("Count: %d", conf.count);
-	debugf("ROM 0: 0x%08x", conf.roms[0]);
-	debugf("ROM 1: 0x%08x", conf.roms[1]);
-	debugf("ROM 2: 0x%08x", conf.roms[2]);
+	for(unsigned i = 0; i < MAX_ROMS; ++i) {
+		debugf("ROM %u: 0x%08x", i, conf.roms[i]);
+	}
 	debugf("GPIO ROM: %d", conf.gpio_rom);
 }
 
diff --git a/samples/Basic_rBoot/basic_rboot.hw b/samples/Basic_rBoot/basic_rboot.hw
index 64465e1b3a..27332cb161 100644
--- a/samples/Basic_rBoot/basic_rboot.hw
+++ b/samples/Basic_rBoot/basic_rboot.hw
@@ -2,11 +2,14 @@
 	"name": "Two ROM slots, two SPIFFS",
 	"base_config": "spiffs",
 	"partitions": {
+		"rom0": {
+			"subtype": "ota_0"
+		},
 		"rom1": {
 			"address": "0x108000",
 			"size": "992K",
 			"type": "app",
-			"subtype": "ota_0",
+			"subtype": "ota_1",
 			"filename": "$(RBOOT_ROM_1_BIN)"
 		},
 		"spiffs1": {
@@ -16,4 +19,4 @@
 			"subtype": "spiffs"
 		}
 	}
-}
+}
\ No newline at end of file
diff --git a/samples/Basic_rBoot/component.mk b/samples/Basic_rBoot/component.mk
index df007c2dfe..9555e50099 100644
--- a/samples/Basic_rBoot/component.mk
+++ b/samples/Basic_rBoot/component.mk
@@ -4,8 +4,4 @@
 RBOOT_ENABLED := 1
 
 ## Use standard hardware config with two ROM slots and two SPIFFS partitions
-ifeq ($(SMING_ARCH),Esp8266)
 HWCONFIG := basic_rboot
-else
-HWCONFIG := spiffs
-endif
diff --git a/samples/HttpServer_FirmwareUpload/app/application.cpp b/samples/HttpServer_FirmwareUpload/app/application.cpp
index 6c97838a30..2dbfe83325 100644
--- a/samples/HttpServer_FirmwareUpload/app/application.cpp
+++ b/samples/HttpServer_FirmwareUpload/app/application.cpp
@@ -38,7 +38,7 @@ int onUpload(HttpServerConnection& connection, HttpRequest& request, HttpRespons
 
 	response.code = HTTP_STATUS_BAD_REQUEST;
 	response.setContentType(MIME_HTML);
-	String html = "<H2 color='#444'>" + OtaUpgradeStream::errorToString(otaStream->errorCode) + "</H2>";
+	String html = "<H2 color='#444'>" + toString(otaStream->errorCode) + "</H2>";
 	response.headers[HTTP_HEADER_CONTENT_LENGTH] = html.length();
 	response.sendString(html);