#include "grub_boot_switch.h" #include "usb_descriptors.h" #include "tinyusb.h" namespace esphome { namespace grub_boot_switch { static const char *const TAG = "grub_boot_switch"; extern GrubBootSwitch *g_active_component; // defined in tusb_glue.cpp static UsbDescriptorBundle s_usb_descriptors; std::string GrubBootSwitch::label_for_digit(uint8_t digit) const { for (const auto &t : targets_) { if (t.first == digit) return t.second; } return std::string(); } bool GrubBootSwitch::digit_for_label(const std::string &label, uint8_t *out) const { for (const auto &t : targets_) { if (t.second == label) { if (out) *out = t.first; return true; } } return false; } void GrubBootSwitch::set_target(uint8_t t) { if (current_target_.load() == t) return; current_target_.store(t); pref_.save(&t); for (auto &cb : callbacks_) cb(t); } void GrubBootSwitch::install_virtual_files_() { // File 1: bare ASCII digit, one byte. Mirrors readSwitch() in the reference. disk_.add_file("switch_position", "SWITCH~1", " ", 1, [this](uint8_t *out) { out[0] = '0' + (current_target_.load() % 10); }); // File 2: GRUB snippet. Length and final-byte position match the reference's // grubConfigStr ("set os_hw_switch=N\n") so existing /etc/grub.d hooks parse // it identically. static const char kPrefix[] = "set os_hw_switch="; static const size_t kPrefixLen = sizeof(kPrefix) - 1; static const size_t kFileLen = kPrefixLen + 2; // digit + newline disk_.add_file("switch_position_grub.cfg", "SWITCH~1", "CFG", kFileLen, [this](uint8_t *out) { memcpy(out, kPrefix, kPrefixLen); out[kPrefixLen] = '0' + (current_target_.load() % 10); out[kPrefixLen + 1] = '\n'; }); } void GrubBootSwitch::start_usb_() { g_active_component = this; usb_descriptors_build(&s_usb_descriptors, usb_vid_, usb_pid_, usb_manufacturer_.c_str(), usb_product_.c_str(), nullptr); tinyusb_config_t cfg = {}; cfg.device_descriptor = reinterpret_cast(s_usb_descriptors.device); cfg.string_descriptor = s_usb_descriptors.strings; cfg.string_descriptor_count = sizeof(s_usb_descriptors.strings) / sizeof(s_usb_descriptors.strings[0]); cfg.external_phy = false; cfg.configuration_descriptor = s_usb_descriptors.config; cfg.self_powered = false; esp_err_t err = tinyusb_driver_install(&cfg); if (err != ESP_OK) { ESP_LOGE(TAG, "tinyusb_driver_install failed: %d", err); this->mark_failed(); return; } ESP_LOGI(TAG, "TinyUSB MSC installed (VID=%04X PID=%04X serial=%s)", usb_vid_, usb_pid_, s_usb_descriptors.serial_buf); } void GrubBootSwitch::setup() { ESP_LOGCONFIG(TAG, "Setting up GRUB boot switch"); // NVS-backed persistence: hash is "grub_boot_switch::target" + a fixed key // so updates stay tied to this component instance. pref_ = global_preferences->make_preference(0x47425354U /* "GBST" */, true); uint8_t saved = current_target_.load(); if (pref_.load(&saved)) { current_target_.store(saved); ESP_LOGCONFIG(TAG, "Restored target from NVS: %u", saved); } install_virtual_files_(); start_usb_(); } void GrubBootSwitch::dump_config() { ESP_LOGCONFIG(TAG, "GRUB boot switch:"); ESP_LOGCONFIG(TAG, " USB VID/PID: %04X:%04X", usb_vid_, usb_pid_); ESP_LOGCONFIG(TAG, " Current target: %u", current_target_.load()); if (!targets_.empty()) { ESP_LOGCONFIG(TAG, " Targets:"); for (const auto &t : targets_) ESP_LOGCONFIG(TAG, " %u: %s", t.first, t.second.c_str()); } } } // namespace grub_boot_switch } // namespace esphome