103 lines
4.0 KiB
Python
103 lines
4.0 KiB
Python
"""ESPHome external_component: GRUB boot switch over USB MSC.
|
|
|
|
Presents a synthetic 64 KB FAT12 disk to the attached host containing two
|
|
virtual files:
|
|
|
|
switch_position - one ASCII digit (0-9) reflecting the current
|
|
boot target.
|
|
switch_position_grub.cfg - GRUB snippet `set os_hw_switch=N\\n` consumed
|
|
by the user's /etc/grub.d/01_bootswitch hook.
|
|
|
|
The volume serial defaults to 0x55AA6922 to stay drop-in compatible with
|
|
the upstream stecman/hw-boot-selection firmware.
|
|
"""
|
|
|
|
from esphome import automation
|
|
import esphome.codegen as cg
|
|
import esphome.config_validation as cv
|
|
from esphome.const import CONF_ID
|
|
from esphome.components import esp32
|
|
|
|
DEPENDENCIES = ["esp32"]
|
|
CODEOWNERS = ["@adamksmith"]
|
|
MULTI_CONF = False
|
|
|
|
grub_boot_switch_ns = cg.esphome_ns.namespace("grub_boot_switch")
|
|
GrubBootSwitch = grub_boot_switch_ns.class_("GrubBootSwitch", cg.Component)
|
|
|
|
CONF_VOLUME_SERIAL = "volume_serial"
|
|
CONF_VOLUME_LABEL = "volume_label"
|
|
CONF_USB_VID = "usb_vid"
|
|
CONF_USB_PID = "usb_pid"
|
|
CONF_USB_MANUFACTURER = "usb_manufacturer"
|
|
CONF_USB_PRODUCT = "usb_product"
|
|
CONF_INITIAL_TARGET = "initial_target"
|
|
CONF_TARGETS = "targets"
|
|
CONF_GRUB_BOOT_SWITCH_ID = "grub_boot_switch_id"
|
|
|
|
|
|
def _validate_label(value):
|
|
value = cv.string_strict(value)
|
|
if len(value) > 11:
|
|
raise cv.Invalid("volume_label must be 11 characters or fewer")
|
|
return value
|
|
|
|
|
|
# targets: maps a digit (0-9) the firmware writes into switch_position to a
|
|
# human-readable label. GRUB scripts react to the digit; the labels are only
|
|
# used by the select sub-platform exposed to Home Assistant.
|
|
TARGETS_SCHEMA = cv.Schema({cv.int_range(min=0, max=9): cv.string_strict})
|
|
|
|
|
|
CONFIG_SCHEMA = cv.All(
|
|
cv.Schema(
|
|
{
|
|
cv.GenerateID(): cv.declare_id(GrubBootSwitch),
|
|
cv.Optional(CONF_VOLUME_SERIAL, default=0x55AA6922): cv.hex_uint32_t,
|
|
cv.Optional(CONF_VOLUME_LABEL, default="SWITCH"): _validate_label,
|
|
cv.Optional(CONF_USB_VID, default=0x26BA): cv.hex_uint16_t,
|
|
cv.Optional(CONF_USB_PID, default=0x8003): cv.hex_uint16_t,
|
|
cv.Optional(CONF_USB_MANUFACTURER, default="Stecman"): cv.string,
|
|
cv.Optional(CONF_USB_PRODUCT, default="Boot Switch"): cv.string,
|
|
cv.Optional(CONF_INITIAL_TARGET, default=0): cv.int_range(min=0, max=9),
|
|
cv.Optional(CONF_TARGETS, default={}): TARGETS_SCHEMA,
|
|
}
|
|
).extend(cv.COMPONENT_SCHEMA),
|
|
cv.only_with_framework("esp-idf"),
|
|
cv.only_on_esp32,
|
|
)
|
|
|
|
|
|
async def to_code(config):
|
|
var = cg.new_Pvariable(config[CONF_ID])
|
|
await cg.register_component(var, config)
|
|
|
|
cg.add(var.set_volume_serial(config[CONF_VOLUME_SERIAL]))
|
|
cg.add(var.set_volume_label(config[CONF_VOLUME_LABEL]))
|
|
cg.add(var.set_usb_ids(config[CONF_USB_VID], config[CONF_USB_PID]))
|
|
cg.add(var.set_usb_strings(config[CONF_USB_MANUFACTURER], config[CONF_USB_PRODUCT]))
|
|
cg.add(var.set_initial_target(config[CONF_INITIAL_TARGET]))
|
|
|
|
for digit, label in sorted(config[CONF_TARGETS].items()):
|
|
cg.add(var.add_target(digit, label))
|
|
|
|
# esp_tinyusb is the supported MSC stack on ESP32-S3 USB-OTG.
|
|
esp32.add_idf_component(
|
|
name="espressif/esp_tinyusb",
|
|
ref="~1.4.0",
|
|
)
|
|
|
|
# Move IDF console off USB-Serial-JTAG so TinyUSB can claim the peripheral.
|
|
esp32.add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_UART_DEFAULT", True)
|
|
esp32.add_idf_sdkconfig_option("CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG", False)
|
|
|
|
# esp_tinyusb's tusb_msc_storage.c provides default tud_msc_* callbacks as
|
|
# strong symbols. We provide our own callback-driven implementations, so
|
|
# we tell the linker to allow multiple definitions and rely on link order
|
|
# (main component first → our symbols win) to pick ours.
|
|
esp32.add_idf_sdkconfig_option("CONFIG_TINYUSB_MSC_ENABLED", True)
|
|
esp32.add_idf_sdkconfig_option("CONFIG_TINYUSB_CDC_ENABLED", False)
|
|
|
|
cg.add_build_flag("-DCFG_TUSB_MCU=OPT_MCU_ESP32S3")
|
|
cg.add_build_flag("-Wl,--allow-multiple-definition")
|