{"record":{"id":"9ea88d41edfb9741","repo":"embassy-rs/embassy","slug":"heartbeat-from-esp32-stopped","errorCode":null,"errorMessage":"heartbeat from esp32 stopped","messagePattern":"heartbeat from esp32 stopped","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"embassy-net-esp-hosted/src/lib.rs","lineNumber":324,"sourceCode":"                        header.copy(&mut buffer);\n                        tx_len = PayloadHeader::SIZE + packet_len;\n                    } else {\n                        // Backend doesn't support the requested interface type. Drop the\n                        // packet and send nothing this iteration.\n                        buffer[..PayloadHeader::SIZE].fill(0);\n                    }\n                }\n                EitherMany::Third(()) => {\n                    buffer[..PayloadHeader::SIZE].fill(0);\n                    tx_len = 0;\n                }\n                EitherMany::Fourth(()) => {\n                    // Extend the deadline if initializing\n                    if let ioctl::ControlState::Reboot | ioctl::ControlState::WaitingForInit = self.shared.state() {\n                        self.heartbeat_deadline = Instant::now() + HEARTBEAT_MAX_GAP;\n                        continue;\n                    }\n                    panic!(\"heartbeat from esp32 stopped\")\n                }\n\n                // Bluetooth HCI packet queued by the host stack.\n                #[cfg(feature = \"bluetooth\")]\n                EitherMany::Fifth(slot) => {\n                    if let Some(if_type_and_num) = self.backend.encode_iface_type(InterfaceType::Hci) {\n                        // `slot.buf[0]` is the H4 packet type indicator; it travels in the\n                        // payload header's `hci_priv_packet_type` field, and the remaining\n                        // bytes are the HCI packet body.\n                        let pkt_type = slot.buf[0];\n                        let body = &slot.buf[1..slot.len];\n                        buffer[PayloadHeader::SIZE..][..body.len()].copy_from_slice(body);\n\n                        let header = PayloadHeader {\n                            if_type_and_num,\n                            len: body.len() as _,\n                            offset: PayloadHeader::SIZE as _,\n                            seq_num: self.next_seq,","sourceCodeStart":306,"sourceCodeEnd":342,"githubUrl":"https://github.com/embassy-rs/embassy/blob/463a07b963419a1bfe61d5d597c44acb810afb8b/embassy-net-esp-hosted/src/lib.rs#L306-L342","documentation":"embassy-net-esp-hosted panics in its main run loop when the ESP32 co-processor stops sending heartbeat responses within HEARTBEAT_MAX_GAP. The driver polls the esp32 (via ioctl control events) and extends the deadline while the chip is initializing; if it is not initializing and the deadline expires, the firmware link is considered dead and the driver aborts, because it cannot recover the radio link. This is a fatal, unrecoverable condition for the driver by design.","triggerScenarios":"The run() loop receives EitherMany::Fourth(()) (heartbeat tick) while shared.state() is neither ControlState::Reboot nor ControlState::WaitingForInit, and the time since the last heartbeat from the ESP32 exceeded HEARTBEAT_MAX_GAP. Practically: the esp32 stopped answering init/control/ioctl exchanges (chip crashed, reset, brown-out, or SPI link failure).","commonSituations":"ESP32 firmware flashed with a mismatched or corrupt esp-hosted firmware image; insufficient power supply causing the esp32 to brown out and reset mid-operation; SPI wiring/soldering issues on custom boards; the esp32 being externally reset (e.g., by another driver or a watchdog); driver started before the esp32 finished booting and it then hung.","solutions":["Verify the ESP32 is running a compatible esp-hosted-ng firmware version flashed for the correct chip (esp32, esp32s3, c3) and that its status LED/boot logs show it is up","Check power supply stability and decoupling capacitors; ESP32 Wi-Fi TX current spikes commonly cause brown-outs and resets","Inspect SPI wiring (MOSI/MISO/CS/CLK/handshake pins) and confirm they match the embassy-net-esp-hosted Config pin settings","Ensure the esp32 reset sequence is honored: reset the chip (or power-cycle the board) before/with driver init so ControlState transitions are observed","Catch the panic in a supervisor task and restart the whole driver + esp32 reset, since the run loop cannot recover once the heartbeat is lost"],"exampleFix":"// before: driver started immediately at boot while esp32 still resetting\nlet device = embassy_net_esp_hosted::new(mio, spi, ctrl_pin, rng, config);\n// after: hold esp32 in reset, then release and wait before init\ndelay.delay(100.millis()); // let esp32 boot fully / recover from reset\nlet device = embassy_net_esp_hosted::new(mio, spi, ctrl_pin, rng, config);","handlingStrategy":"retry","validationCode":"// before driver init: verify esp32 responds and is flashed\n// (embedded: no pre-call check possible; validate at startup)\nfn validate_esp32(ctrl: &mut Control) {\n    match ctrl.init() {\n        Ok(_) => info!(\"esp32 alive\"),\n        Err(e) => panic!(\"esp32 not responding, check power/spi/firmware: {:?}\", e),\n    }\n}","typeGuard":null,"tryCatchPattern":"// Rust panics cannot be caught in no_std; wrap in a supervisor task\n// that resets the esp32 and re-creates the driver on watchdog expiry\nloop {\n    spawn_network_task();\n    if WATCHDOG.timed_out() {\n        esp32_reset_pin.set_low(); // hard reset co-processor\n        Timer::after(100.millis()).await;\n        esp32_reset_pin.set_high();\n    }\n}","preventionTips":["Use a compatible esp-hosted-ng firmware version for your driver release","Provide a stable power supply with adequate decoupling for esp32 Wi-Fi current spikes","Verify SPI pin assignments in Config against the physical wiring","Add a hardware watchdog that resets the esp32 (reset pin) if the driver stops","Wait for esp32 boot completion before initializing the driver"],"tags":["embedded","esp32","wifi","hardware","panic"],"backgroundTag":"internal-invariant-violation","analyzedSha":"463a07b963419a1bfe61d5d597c44acb810afb8b","analyzedAt":"2026-09-10T13:38:26.660Z","contentChangedAt":"2026-09-10T13:38:26.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}