use super::ffi;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BdbSignal {
SkipStartup,
DeviceFirstStart { ok: bool },
DeviceReboot { ok: bool },
Steering { ok: bool },
TouchlinkStarted,
TouchlinkJoinedRouter,
CanSleep,
Leave,
DeviceAnnounce,
MacSplitBoot,
NlmeStatusIndication,
PermitJoinStatus,
DeviceUnavailable,
Unknown(u32),
}
impl BdbSignal {
pub fn from_raw(signal_type: u32, status: i32) -> Self {
let ok = status == 0;
match signal_type {
ffi::ESP_ZB_ZDO_SIGNAL_SKIP_STARTUP => Self::SkipStartup,
ffi::ESP_ZB_BDB_SIGNAL_DEVICE_FIRST_START => Self::DeviceFirstStart { ok },
ffi::ESP_ZB_BDB_SIGNAL_DEVICE_REBOOT => Self::DeviceReboot { ok },
ffi::ESP_ZB_BDB_SIGNAL_STEERING => Self::Steering { ok },
ffi::ESP_ZB_BDB_SIGNAL_TOUCHLINK_NWK_STARTED => Self::TouchlinkStarted,
ffi::ESP_ZB_BDB_SIGNAL_TOUCHLINK_NWK_JOINED_ROUTER => Self::TouchlinkJoinedRouter,
ffi::ESP_ZB_COMMON_SIGNAL_CAN_SLEEP => Self::CanSleep,
ffi::ESP_ZB_ZDO_SIGNAL_LEAVE => Self::Leave,
ffi::ESP_ZB_ZDO_SIGNAL_DEVICE_ANNCE => Self::DeviceAnnounce,
ffi::ESP_ZB_MACSPLIT_DEVICE_BOOT => Self::MacSplitBoot,
ffi::ESP_ZB_NLME_STATUS_INDICATION => Self::NlmeStatusIndication,
ffi::ESP_ZB_NWK_SIGNAL_PERMIT_JOIN_STATUS => Self::PermitJoinStatus,
ffi::ESP_ZB_ZDO_DEVICE_UNAVAILABLE => Self::DeviceUnavailable,
other => Self::Unknown(other),
}
}
}
static mut REJOIN_ATTEMPT: u8 = 0;
unsafe extern "C" fn steering_retry_cb(mode: u8) {
esp_zb_bdb_start_top_level_commissioning_checked(mode);
}
fn esp_zb_bdb_start_top_level_commissioning_checked(mode: u8) {
let ret = unsafe { ffi::esp_zb_bdb_start_top_level_commissioning(mode) };
if ret != 0 {
log::error!("Failed to start commissioning (mode {}): {}", mode, ret);
}
}
static mut JOINED_HOOK: unsafe fn() = default_joined_hook;
unsafe fn default_joined_hook() {}
pub unsafe fn set_joined_hook(hook: unsafe fn()) {
JOINED_HOOK = hook;
}
pub fn handle_bdb_signal(signal: BdbSignal) {
match signal {
BdbSignal::SkipStartup => {
log::info!("Zigbee stack initialized, starting BDB initialization");
esp_zb_bdb_start_top_level_commissioning_checked(ffi::BDB_MODE_INITIALIZATION);
}
BdbSignal::DeviceFirstStart { ok: true } | BdbSignal::DeviceReboot { ok: true } => {
unsafe {
REJOIN_ATTEMPT = 0;
}
let factory_new = unsafe { ffi::esp_zb_bdb_is_factory_new() };
if factory_new {
log::info!("Factory-new device, starting network steering");
esp_zb_bdb_start_top_level_commissioning_checked(ffi::BDB_MODE_NETWORK_STEERING);
} else {
log::info!("Device rebooted (already commissioned) — resuming");
unsafe { JOINED_HOOK() };
}
}
BdbSignal::DeviceFirstStart { ok: false } => {
log::warn!("BDB first start failed, retrying steering in 1s...");
unsafe {
ffi::esp_zb_scheduler_alarm(
steering_retry_cb,
ffi::BDB_MODE_NETWORK_STEERING,
1_000,
);
}
}
BdbSignal::DeviceReboot { ok: false } => {
let attempt = unsafe {
REJOIN_ATTEMPT = REJOIN_ATTEMPT.saturating_add(1);
REJOIN_ATTEMPT
};
if attempt < 6 {
log::warn!(
"Network not found (attempt {}/6) — retrying in 10s...",
attempt
);
unsafe {
ffi::esp_zb_scheduler_alarm(
steering_retry_cb,
ffi::BDB_MODE_INITIALIZATION,
10_000,
);
}
} else {
log::warn!("Network not found after ~60s — opening for new Hub (NVM preserved)");
unsafe {
REJOIN_ATTEMPT = 0;
ffi::esp_zb_scheduler_alarm(
steering_retry_cb,
ffi::BDB_MODE_NETWORK_STEERING,
1_000,
);
}
}
}
BdbSignal::Steering { ok: true } => {
unsafe {
REJOIN_ATTEMPT = 0;
}
let ch = unsafe { ffi::esp_zb_get_current_channel() };
let pan = unsafe { ffi::esp_zb_get_pan_id() };
let addr = unsafe { ffi::esp_zb_get_short_address() };
log::info!(
"Network steering succeeded — joined network! ch={} PAN=0x{:04X} addr=0x{:04X}",
ch,
pan,
addr,
);
unsafe { JOINED_HOOK() };
}
BdbSignal::Steering { ok: false } => {
log::warn!("Network steering failed, retrying in 1s...");
unsafe {
ffi::esp_zb_scheduler_alarm(
steering_retry_cb,
ffi::BDB_MODE_NETWORK_STEERING,
1000,
);
}
}
BdbSignal::TouchlinkStarted | BdbSignal::TouchlinkJoinedRouter => {
log::info!("Touchlink commissioning event");
}
BdbSignal::CanSleep => {}
BdbSignal::Leave => {
log::warn!("Left network");
}
BdbSignal::DeviceAnnounce => {
log::debug!("Device announcement received");
}
BdbSignal::MacSplitBoot => {
log::debug!("MAC co-processor boot");
}
BdbSignal::NlmeStatusIndication => {
log::debug!("NWK status indication");
}
BdbSignal::PermitJoinStatus => {
log::debug!("Permit-join status changed");
}
BdbSignal::DeviceUnavailable => {
log::debug!("ZDO device unavailable");
}
BdbSignal::Unknown(sig) => {
log::info!("ZDO signal: 0x{:02x}", sig);
}
}
}