Skip to content

Factory Update Actions Dispatch

The factory_update_actions_dispatch module is the central action router inside the factory recovery application. It sits at the junction between the menu/input system and the concrete update operations, translating a user’s menu selection into a concrete action — either switching the active OTA boot partition or delegating to the SD-card flash pipeline.

It owns three focused responsibilities:

ResponsibilityFunctions
Low-level OTA partition switchboot_partition()
UI-aware boot action with user feedbackaction_boot_partition()
Menu-action dispatch switchexecute_selected_action()

The module is present identically across every board variant: esp32_2432s028r, esp32_3248s035c/r, esp32s3_4827s043c, esp32s3_8048_touch_lcd_7, esp32s3_8048s043c/050c/070c, esp32s3_bzm_tft35_gt911, esp32s3_hmi43v3, esp32s3_zx3d50ce02s_usrc_4832, and pibot_pendant_v1_0.


Factory Application

factory_update_actions

BTN_3 / touch OK

BOOT_APP0 / APP1

SD_UPDATE_APP0/APP1/RES

factory_update_actions_otadata

(restore_otadata_from_backup)

factory_app_entry

(app_main)

factory_menu_system

(draw_menu, menu_move, menu_select)

factory_input_dispatch

(dispatch_button, touch_hint_hit_test)

factory_update_actions_dispatch

(boot_partition, action_boot_partition,

execute_selected_action)

factory_update_actions_sd_flash

(action_sd_update, action_sd_update_res,

probe_sd_files)

factory_visual_feedback

(draw_progress, draw_result,

draw_flashing_screen)

factory_hardware_drivers

(buttons, encoder, touch, LCD, SD)

BTN_3

runs before dispatch

(at startup)

SD actions

status messages

factory_input_dispatch

dispatch_button()

factory_update_actions_dispatch

execute_selected_action()

action_boot_partition()

boot_partition()

factory_update_actions_otadata

restore_otadata_from_backup()

factory_update_actions_sd_flash

action_sd_update()

action_sd_update_res()

probe_sd_files()

factory_visual_feedback

show_status()

draw_menu()

draw_result()

ESP-IDF OTA API

esp_ota_set_boot_partition()

esp_restart()


boot_partition(label) — Low-Level OTA Partition Switch

Section titled “boot_partition(label) — Low-Level OTA Partition Switch”

Signature: static void boot_partition(const char *label)

The primitive that sets the next boot target and immediately reboots. It is a one-way door: on success it calls esp_restart() and never returns. On failure (partition not found, OTA API error) it logs the error and returns to the caller so the UI can display a failure message.

static void boot_partition(const char *label)
{
const esp_partition_t *part = esp_partition_find_first(
ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_ANY, label);
if (!part) {
ESP_LOGE(TAG, "Partition '%s' not found", label);
return;
}
esp_err_t err = esp_ota_set_boot_partition(part);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set boot partition: %s", esp_err_to_name(err));
return;
}
FACTORY_LOGD(TAG, "Boot partition set to '%s', rebooting...", label);
esp_restart();
}

Precondition: The otadata sector must have been restored by restore_otadata_from_backup() (see factory_update_actions_otadata.md) before this function is called. Without a valid otadata, esp_ota_set_boot_partition() may silently mark the wrong slot or corrupt the boot chain.

Partition labels used:

LabelSlotAvailability
"app0"Primary OTA slotAlways present
"app1"Secondary OTA slot8 MB boards only

action_boot_partition(label) — UI-Aware Boot Wrapper

Section titled “action_boot_partition(label) — UI-Aware Boot Wrapper”

Signature: static void action_boot_partition(const char *label)

Wraps boot_partition() with user-visible feedback: displays a cyan status message, waits 500 ms so the user can read it, then delegates to boot_partition(). If the call returns (failure path), the menu is redrawn and a red error message is shown.

show_status("Booting <label>...", CYAN)
│
vTaskDelay(500 ms)
│
boot_partition(label)
│
success ──→ [esp_restart — never returns]
failure ──→ draw_menu() → show_status("<label> not found!", RED)

This is the only boot action exposed to execute_selected_action(); the bare boot_partition() primitive is never called directly from the dispatch layer.


execute_selected_action() — Central Dispatch Switch

Section titled “execute_selected_action() — Central Dispatch Switch”

Signature: static void execute_selected_action(void)

Reads menu_items[menu_selected].action (a menu_action_t enum value built at startup by app_main) and routes to the correct handler.

static void execute_selected_action(void)
{
switch (menu_items[menu_selected].action) {
case MENU_ACTION_BOOT_APP0: action_boot_partition("app0"); break;
case MENU_ACTION_BOOT_APP1: action_boot_partition("app1"); break;
case MENU_ACTION_SD_UPDATE_APP0: action_sd_update("app0"); break;
case MENU_ACTION_SD_UPDATE_APP1: action_sd_update("app1"); break;
case MENU_ACTION_SD_UPDATE_RES: action_sd_update_res(); break;
}
}

action_sd_update() and action_sd_update_res() are defined in factory_update_actions_sd_flash.md.

Defined identically in every board’s main.c:

ValueMeaningHandled by
MENU_ACTION_BOOT_APP0Reboot into app0action_boot_partition("app0")
MENU_ACTION_BOOT_APP1Reboot into app1action_boot_partition("app1")
MENU_ACTION_SD_UPDATE_APP0Flash esp3dfw.bin → app0action_sd_update("app0")
MENU_ACTION_SD_UPDATE_APP1Flash esp3dfw.bin → app1action_sd_update("app1")
MENU_ACTION_SD_UPDATE_RESFlash ui_resources.bin → ui_resources partitionaction_sd_update_res()

Items for app1 and the corresponding SD update are only added to menu_items[] when an app1 partition is detected at startup via esp_partition_find_first(). On 4 MB boards the switch never reaches those two cases.


factory_visual_feedback(show_status / draw_menu)ESP-IDF OTA APIboot_partitionaction_boot_partitionfactory_update_actions_dispatch(execute_selected_action)factory_input_dispatch(dispatch_button)factory_visual_feedback(show_status / draw_menu)ESP-IDF OTA APIboot_partitionaction_boot_partitionfactory_update_actions_dispatch(execute_selected_action)factory_input_dispatch(dispatch_button)System reboots - no returnalt[Partition found][Partition not found or OTA error]execute_selected_action()action_boot_partition("app0" | "app1")show_status("Booting ...", CYAN)vTaskDelay(500 ms)boot_partition("app0" | "app1")esp_partition_find_first()esp_ota_set_boot_partition()esp_restart()return (failure)draw_menu()show_status("... not found!", RED) factory_update_actions_sd_flash(action_sd_update / action_sd_update_res)factory_update_actions_dispatch(execute_selected_action)factory_input_dispatchfactory_update_actions_sd_flash(action_sd_update / action_sd_update_res)factory_update_actions_dispatch(execute_selected_action)factory_input_dispatchFull SD flash pipelineSee factory_update_actions_sd_flash.mdexecute_selected_action()action_sd_update("app0" | "app1")OR action_sd_update_res()

Process Flow — Full Recovery Menu Lifecycle

Section titled “Process Flow — Full Recovery Menu Lifecycle”

encoder / button / touch event

BTN_1 Up

BTN_2 Down

BTN_3 OK

BOOT_APP0

BOOT_APP1

SD_UPDATE_APP0

SD_UPDATE_APP1

SD_UPDATE_RES

success

failure

success

failure

success

failure

success

failure

success

failure

app_main starts

restore_otadata_from_backup

init hardware / LCD / touch

detect partitions / build menu_items

probe_sd_files

draw_menu

Event loop

dispatch_button

menu_move -1

menu_move +1

execute_selected_action()

menu_action_t

action_boot_partition('app0')

action_boot_partition('app1')

action_sd_update('app0')

action_sd_update('app1')

action_sd_update_res

esp_restart


The dispatch logic and the menu_action_t enum are identical on every supported board. Differences are board-specific and localised elsewhere:

AspectWhat differsOwned by
LCD driverst7796_init() / st7262_init() / ili9341_init()factory_hardware_drivers
Touch hit-test column mathWide-canvas boards use icon-centre midpoints instead of screen-thirdsfactory_input_dispatch
Recovery triggerBootloader hook (pibot) vs software [ESP444]FACTORY command (all others)custom_bootloader
app1 availabilityMenu items conditionally added at startup based on partition probefactory_app_entry (app_main)

All boards apply the same OTADATA_BACKUP_OFFSET = 0xB000 and BACKUP_MAGIC = 0xAA55AA55. These values must match the main firmware’s esp444.cpp exactly.


ModuleRole
factory_update_actions_otadataRestores otadata before any boot action runs
factory_update_actions_sd_flashImplements action_sd_update() and action_sd_update_res() — the full SD flash pipeline
factory_input_dispatchCalls execute_selected_action() when BTN_3 / touch-OK fires
factory_menu_systemProvides menu_items[], menu_selected, draw_menu(), show_status(), clear_status()
factory_visual_feedbackdraw_flashing_screen(), draw_progress(), draw_result() used by SD flash actions
APIPurpose
esp_partition_find_first()Locate an OTA partition by label
esp_ota_set_boot_partition()Write the next-boot target into otadata
esp_restart()Trigger an immediate system reboot
esp_ota_get_boot_partition()Query the currently active OTA slot (used by get_active_ota_label() for the menu header)

boot_partition() is a one-way door by design. On success it reboots immediately. The UI-aware wrapper action_boot_partition() handles the failure path so that boot_partition() stays minimal and free of UI concerns. Any caller that needs fallback behaviour must check whether boot_partition() returned.

execute_selected_action() owns no state. It only reads menu_items[menu_selected].action and dispatches. All SD-flash state (file handles, OTA handles, byte counters, progress) lives inside factory_update_actions_sd_flash. All menu cursor state lives inside factory_menu_system. This keeps the dispatch layer thin and easy to verify.

app1 items are omitted at runtime, not at compile time. The has_app1 flag is set by probing the partition table at startup. A single firmware binary works correctly on both 4 MB (app0-only) and 8 MB (app0 + app1) flash variants without needing separate builds or #ifdef guards in the dispatch switch.

Otadata must be restored before this module runs. restore_otadata_from_backup() is called as the very first statement in app_main, before hardware init. If this step is skipped, esp_ota_set_boot_partition() would write into a stale or erased otadata sector, leaving the device unable to boot correctly after recovery completes.

CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED=y is required in the factory sdkconfig. The otadata backup at 0xB000 lies below the first partition. ESP-IDF aborts flash writes to that range by default (CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS). Dangerous writes are enabled intentionally — the factory application is a privileged recovery tool with direct flash access, not a normal user-space app.