use esp_idf_hal::gpio::{AnyInputPin, AnyOutputPin, Pull};
use esp_idf_hal::peripherals::Peripherals;
use huesmith_core::hue::identity::DeviceIdentity;
use huesmith_core::light::state::LightState;
use huesmith_core::light::LightOutput;
use crate::light::{cct::CctPwm, simple::SimpleLed, ws2812::Ws2812b};
use crate::{Light, LightKind};
pub(crate) fn launch(mut light: Light) -> ! {
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();
unsafe {
let ret = esp_idf_sys::nvs_flash_init();
if ret == esp_idf_sys::ESP_ERR_NVS_NO_FREE_PAGES
|| ret == esp_idf_sys::ESP_ERR_NVS_NEW_VERSION_FOUND
{
esp_idf_sys::nvs_flash_erase();
let retry = esp_idf_sys::nvs_flash_init();
if retry != esp_idf_sys::ESP_OK {
log::error!(
"nvs_flash_init failed after erase: {retry} — Zigbee state may not persist"
);
}
}
}
log::info!("╔═══════════════════════════════════════════════╗");
log::info!("║ huesmith v{:<35}║", env!("CARGO_PKG_VERSION"));
log::info!("║ Hue-compatible Zigbee Light ║");
log::info!("╚═══════════════════════════════════════════════╝");
log::info!(
"Device: {} {} ({:?})",
light.identity.manufacturer_name,
light.identity.model_identifier,
light.kind
);
let p = Peripherals::take().expect("peripherals already taken");
let nvram_erase = if let Some(reset_gpio) = light.reset_pin {
let pin = unsafe { AnyInputPin::steal(reset_gpio) };
match esp_idf_hal::gpio::PinDriver::input(pin, Pull::Up) {
Ok(driver) => {
let is_low = driver.is_low();
if is_low {
log::warn!(
"Factory reset: GPIO{} held low at boot — erasing Zigbee NVRAM",
reset_gpio
);
}
is_low
}
Err(e) => {
log::error!("Failed to configure reset pin GPIO{}: {e}", reset_gpio);
false
}
}
} else {
false
};
let led_output: Box<dyn LightOutput> = match light.kind {
LightKind::Dimmable { gpio } | LightKind::OnOff { gpio } => {
log::info!(
"LED: PWM on GPIO{}{}",
gpio,
if light.inverted { " (inverted)" } else { "" }
);
let pin = unsafe { AnyOutputPin::steal(gpio) };
Box::new(SimpleLed::new(
p.ledc.channel0,
p.ledc.timer0,
pin,
light.inverted,
))
}
LightKind::Cct {
cool_gpio,
warm_gpio,
} => {
log::info!(
"LED: CCT PWM cool=GPIO{} warm=GPIO{}{}",
cool_gpio,
warm_gpio,
if light.inverted { " (inverted)" } else { "" }
);
let cool_pin = unsafe { AnyOutputPin::steal(cool_gpio) };
let warm_pin = unsafe { AnyOutputPin::steal(warm_gpio) };
Box::new(CctPwm::new(
p.ledc.timer0,
p.ledc.channel0,
cool_pin,
p.ledc.channel1,
warm_pin,
light.inverted,
))
}
LightKind::Color { gpio } => {
log::info!(
"LED: WS2812B on GPIO{} ({} LED{})",
gpio,
light.leds,
if light.leds != 1 { "s" } else { "" },
);
let pin = unsafe { AnyOutputPin::steal(gpio) };
#[allow(deprecated)]
let channel = p.rmt.channel0;
let active = light.active_leds.unwrap_or(light.leds).min(light.leds);
Box::new(Ws2812b::new(channel, pin, light.leds, active))
}
LightKind::Custom { device_type } => {
log::info!("LED: user-supplied LightOutput backend ({:?})", device_type);
light
.custom_output
.take()
.expect("Light::custom always sets custom_output")
}
};
let light_state = if light.power_on {
LightState::new_powered_on(led_output)
} else {
LightState::new(led_output)
};
let identity: &'static DeviceIdentity = Box::leak(Box::new(light.identity));
let config: &'static crate::zigbee::Config = Box::leak(Box::new(crate::zigbee::Config {
device_type: light.kind.device_type(),
identity,
mac: light.mac,
channel: light.channel,
endpoint_id: 11,
}));
log::info!(
"Zigbee: endpoint {}, {:?}, starting...",
config.endpoint_id,
config.device_type,
);
crate::zigbee::run(config, light_state, nvram_erase)
}