DFRobot ESP32-C6 Guru Mediation Error: Core 0 panic'ed (Interrupt wdt timeout on CPU0)

I am building a fall detector with the DFRobot Beetle ESP32-C6 and the BMI160. It generally works well, except when I place it in service mode for re-flashing, etc. If I leave it on in service mode, after a few hours or less it will panic. The Log when it panics are as follows, and the crash always happens with the following lines:

[03:14:37][W][api.connection:2440]: Home Assistant 2026.3.3 (192.168.2.118): is unresponsive; disconnecting
[03:14:37][W][component:398]: api set Warning flag: unspecified
[03:14:37][D][api:222]: Accept 192.168.2.118
[03:14:37][W][component:429]: api cleared Warning flag
[03:14:37][D][api.connection:2440]: Home Assistant 2026.3.3 (192.168.2.118): connected
[03:14:37][D][time:104]: Synchronized time: 2026-04-10 03:14:37
[03:14:37][D][homeassistant.binary_sensor:025]: 'input_boolean.fall_detector_service_mode': Got state ON
[03:14:37]Guru Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU0). 

Fuller log below

[03:10:36][D][text_sensor:120]: 'Fall Detector Heartbeat' >> 'Fri 2026-04-10 03:10:36'
[03:10:38][D][sensor:124]: 'WiFi Signal Strength' >> -54 dBm
[03:11:32][D][sensor:124]: 'Battery Voltage' >> 4.14 V
[03:11:32][D][sensor:124]: 'Battery Capacity' >> 100 %
[03:11:40][D][text_sensor:120]: 'Fall Detector Heartbeat' >> 'Fri 2026-04-10 03:11:40'
[03:11:41][D][sensor:124]: 'WiFi Signal Strength' >> -54 dBm
[03:12:32][D][sensor:124]: 'Battery Voltage' >> 4.14 V
[03:12:32][D][sensor:124]: 'Battery Capacity' >> 100 %
[03:12:44][D][sensor:124]: 'WiFi Signal Strength' >> -54 dBm
[03:12:44][D][text_sensor:120]: 'Fall Detector Heartbeat' >> 'Fri 2026-04-10 03:12:44'
[03:13:32][D][sensor:124]: 'Battery Voltage' >> 4.14 V
[03:13:32][D][sensor:124]: 'Battery Capacity' >> 100 %
[03:13:47][D][sensor:124]: 'WiFi Signal Strength' >> -54 dBm
[03:13:48][D][text_sensor:120]: 'Fall Detector Heartbeat' >> 'Fri 2026-04-10 03:13:48'
[03:14:32][D][sensor:124]: 'Battery Voltage' >> 4.09 V
[03:14:32][D][sensor:124]: 'Battery Capacity' >> 99 %
[03:14:37][W][api.connection:2440]: Home Assistant 2026.3.3 (192.168.2.118): is unresponsive; disconnecting
[03:14:37][W][component:398]: api set Warning flag: unspecified
[03:14:37][D][api:222]: Accept 192.168.2.118
[03:14:37][W][component:429]: api cleared Warning flag
[03:14:37][D][api.connection:2440]: Home Assistant 2026.3.3 (192.168.2.118): connected
[03:14:37][D][time:104]: Synchronized time: 2026-04-10 03:14:37
[03:14:37][D][homeassistant.binary_sensor:025]: 'input_boolean.fall_detector_service_mode': Got state ON
[03:14:37]Guru Meditation Error: Core  0 panic'ed (Interrupt wdt timeout on CPU0). 
[03:14:37]
[03:14:37]Core  0 register dump:
[03:14:37]MEPC    : 0x40017606  RA      : 0x40807292  SP      : 0x4082efb0  GP      : 0x408194e4  
[03:14:37]TP      : 0x4082f0a0  T0      : 0x400208f0  T1      : 0x001f198f  T2      : 0x00002000  
[03:14:37]S0/FP   : 0x40822000  S1      : 0x00000000  A0      : 0x83b811e0  A1      : 0xffffffff  
[03:14:37]A2      : 0x000fffff  A3      : 0x01f19a7f  A4      : 0xc3290ca4  A5      : 0x02dad3b8  
[03:14:37]A6      : 0x00000001  A7      : 0x00000000  S2      : 0x40822000  S3      : 0x40880000  
[03:14:37]S4      : 0x00000000  S5      : 0x40880000  S6      : 0x40880000  S7      : 0x40880000  
[03:14:37]S8      : 0x40880000  S9      : 0x00000000  S10     : 0x00000000  S11     : 0x00000000  
[03:14:37]T3      : 0x00000014  T4      : 0x40822000  T5      : 0x40822000  T6      : 0x00000003  
[03:14:37]MSTATUS : 0x00001881  MTVEC   : 0x40800001  MCAUSE  : 0x00000018  MTVAL   : 0xfea7ede3  
[03:14:37]MHARTID : 0x00000000  
[03:14:37]
[03:14:37]Stack memory:
[03:14:37]4082efb0: 0x00000001 0x00000001 0x4081a524 0x420abcda 0x40822000 0x00000001 0x40822000 0x420aa3b4
[03:14:37]4082efd0: 0x40822000 0x00000001 0x00000001 0x408077e4 0x4081b000 0x40880000 0x40880000 0x40016b48
[03:14:37]4082eff0: 0x4081b000 0x40880000 0x40821000 0x40814492 0x00cb0000 0x4083ae00 0x80000000 0xffffffff
[03:14:37]4082f010: 0x4081b0a4 0x420e6500 0x4082a228 0x420a9186 0x4081b0a4 0x40880000 0x00000000 0x40813ece
[03:14:37]4082f030: 0x4081b0a4 0x420e6500 0x40880000 0x4080e112 0x00000000 0x00000000 0x00000008 0x4082a228
[03:14:37]4082f050: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f070: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f090: 0x00000000 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0xa5a5a5a5 0x00000150 0x4082ef30
[03:14:37]4082f0b0: 0x00000000 0x4081d688 0x4081d688 0x4082f0ac 0x4081d680 0x00000002 0x4082cfe4 0x4082cfe4
[03:14:37]4082f0d0: 0x4082f0ac 0x00000000 0x00000017 0x4082d6a8 0x69666977 0x00000000 0x00000000 0x00000000
[03:14:37]4082f0f0: 0x4082f0a0 0x00000017 0x00000001 0x00000000 0x00000000 0x00000000 0x40822e04 0x40822e6c
[03:14:37]4082f110: 0x40822ed4 0x00000000 0x00000000 0x00000001 0x00000000 0x00000000 0x00000000 0x42051808
[03:14:37]4082f130: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f150: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f170: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f190: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f1b0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f1d0: 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
[03:14:37]4082f1f0: 0x00000000 0x00000000 0x00000000 0x00000054 0x4082f200 0x4082f200 0x4082f200 0x4082f200
[03:14:37]4082f210: 0x00000000 0x4082f218 0xffffffff 0x4082f218 0x4082f218 0x00000000 0x4082f22c 0xffffffff
[03:14:37]4082f230: 0x4082f22c 0x4082f22c 0x00000000 0x00000001 0x00000000 0x0000ffff 0x00000000 0x00000000
[03:14:37]4082f250: 0x4082f1f8 0x00001110 0x6f6d706f 0x00006564 0x2e617473 0x64697373 0x00000000 0x2e617473
[03:14:37]4082f270: 0x68747561 0x65646f6d 0x00010000 0x00000000 0x00000004 0x00000002 0x4081eaa0 0x2e617473
[03:14:37]4082f290: 0x64697373 0x00000000 0x2e617473 0x68747561 0x65646f6d 0x00000000 0x2e617473 0x00240701
[03:14:37]4082f2b0: 0x00000000 0x00000000 0x00000000 0x4081eaa4 0x2e617473 0x68747561 0x65646f6d 0x00000000
[03:14:37]4082f2d0: 0x2e617473 0x64777370 0x00000000 0x2e617473 0x00010002 0x00000000 0x00000010 0x00000000
[03:14:37]4082f2f0: 0x4081eace 0x2e617473 0x64777370 0x00000000 0x2e617473 0x006b6d70 0x2e617473 0x6e616863
[03:14:37]4082f310: 0x00000000 0x00410603 0x00000000 0x00000000 0x00000000 0x4081eacf 0x2e617473 0x006b6d70
[03:14:37]4082f330: 0x2e617473 0x6e616863 0x00000000 0x6f747561 0x6e6f632e 0x0000006e 0x00200704 0x00000000
[03:14:37]4082f350: 0x00000000 0x00000000 0x4081eb10 0x2e617473 0x6e616863 0x00000000 0x6f747561 0x6e6f632e
[03:14:37]4082f370: 0x0000006e 0x69737362 0x65732e64 0x00010005 0x00000000 0x0000000e 0x00000000 0x4081eb30
[03:14:37]4082f390: 0x6f747561 0x6e6f632e 0x0000006e 0x69737362 0x65732e64 0x00000074 0x2e617473 0x69737362
[03:14:37]
[03:14:37]
[03:14:37]
[03:14:37]ELF file SHA256: 094454315
[03:14:37]
[03:14:37]Rebooting...
[03:14:37]ESP-ROM:esp32c6-20220919
[03:14:37]Build:Sep 19 2022
[03:14:37]rst:0xc (SW_CPU),boot:0x4c (SPI_FAST_FLASH_BOOT)
[03:14:37]Saved PC:0x4001975a
[03:14:37]SPIWP:0xee
[03:14:37]mode:DIO, clock div:2
[03:14:37]load:0x40875730,len:0x1734
[03:14:37]load:0x4086b910,len:0xe78
[03:14:37]load:0x4086e610,len:0x31a0
[03:14:37]entry 0x4086b910
[03:14:37]I (22) boot: ESP-IDF 5.5.3 2nd stage bootloader
[03:14:37]I (22) boot: compile time Apr  8 2026 20:58:26
[03:14:37]I (23) boot: chip revision: v0.2
[03:14:37]I (23) boot: efuse block revision: v0.3
[03:14:37]I (24) boot.esp32c6: SPI Speed      : 80MHz
[03:14:37]I (24) boot.esp32c6: SPI Mode       : DIO
[03:14:37]I (24) boot.esp32c6: SPI Flash Size : 4MB
[03:14:37]I (25) boot: Enabling RNG early entropy source...
[03:14:37]I (26) boot: Partition Table:
[03:14:37]I (26) boot: ## Label            Usage          Type ST Offset   Length
[03:14:37]I (27) boot:  0 otadata          OTA data         01 00 00009000 00002000
[03:14:37]I (27) boot:  1 phy_init         RF data          01 01 0000b000 00001000
[03:14:37]I (28) boot:  2 app0             OTA app          00 10 00010000 001c0000
[03:14:37]I (29) boot:  3 app1             OTA app          00 11 001d0000 001c0000
[03:14:37]I (30) boot:  4 nvs              WiFi data        01 02 00390000 0006d000
[03:14:37]I (31) boot: End of partition table
[03:14:37]I (32) esp_image: segment 0: paddr=00010020 vaddr=420c0020 size=293bch (168892) map
[03:14:37]I (95) esp_image: segment 1: paddr=000393e4 vaddr=40800000 size=06c34h ( 27700) load
[03:14:38]I (108) esp_image: segment 2: paddr=00040020 vaddr=42000020 size=bdac4h (776900) map
[03:14:38]I (396) esp_image: segment 3: paddr=000fdaec vaddr=40806c34 size=120a8h ( 73896) load
[03:14:38]I (428) esp_image: segment 4: paddr=0010fb9c vaddr=40818ce0 size=039e4h ( 14820) load
[03:14:38]I (435) esp_image: segment 5: paddr=00113588 vaddr=50000000 size=00064h (   100) load
[03:14:38]I (447) boot: Loaded app from partition at offset 0x10000
[03:14:38]I (448) boot: Disabling RNG early entropy source...
[03:14:38][I][logger:120]: Log initialized
[03:14:38][I][app:089]: Running through setup()
[03:14:38][W][i2c.idf:027]: Using max allowed timeout: 13 ms
[03:14:38][I][i2c.idf:205]: Performing bus recovery
[03:14:38][D][binary_sensor:048]: 'I2C Error' >> OFF
[03:14:38][D][switch:021]: 'BMI160 Power Switch' Turning ON.
[03:14:38][D][switch:064]: 'BMI160 Power Switch' >> ON
[03:14:38][D][switch:021]: 'BMI160 Power Switch' Turning ON.
[03:14:38][D][number:025]: 'High G Sensitivity (G)' >> 2.50
[03:14:38][I][BMI160:478]: Sensitivity updated to 2.5G (Byte: 0x50)
[03:14:38][D][number:025]: 'Low-G Duration (ms)' >> 200.00
[03:14:38][D][number:025]: 'Low-G Threshold (mg)' >> 250.00
[03:14:38][D][number:025]: 'High-G Duration (ms)' >> 100.00
[03:14:38][D][number:025]: 'High-G Hysteresis' >> 2.00
[03:14:38][D][number:025]: 'Low-G Hysteresis' >> 1.00
[03:14:38][C][wifi:631]: Starting
[03:14:38][D][wifi:2231]: Loaded fast_connect settings
[03:14:38][I][wifi:681]: Starting fast_connect (saved) [redacted]
[03:14:38][I][wifi:1088]: Connecting to [redacted] [redacted] (priority 0, attempt 1/1 in phase INITIAL_CONNECT)...
[03:14:38][C][component:252]: Setup wifi took 87ms
[03:14:38][W][component:398]: api set Warning flag: unspecified
[03:14:38][I][deep_sleep:019]: Scheduling in 60000 ms
[03:14:38][I][app:138]: setup() finished successfully!
[03:14:38][D][binary_sensor:048]: 'Fall Motion Detected' >> OFF
[03:14:38][D][main:279]: Switch LED to healthy afer emergency cleared
[03:14:38][D][main:282]: ✅ Fall Alert Cleared - Device moved or timer expired
[03:14:38][D][binary_sensor:048]: 'Person Still After Fall' >> OFF
[03:14:38][I][main:655]: Resetting fall sensors
[03:14:38][W][component:520]: interval took a long time for an operation (178 ms), max is 30 ms
[03:14:38][W][component:520]: logger took a long time for an operation (179 ms), max is 30 ms
[03:14:38][W][wifi_esp32:781]: Disconnected ssid='ace-2.4' bssid=[redacted] reason='Authentication Failed'
[03:14:38][W][component:404]: wifi set Warning flag: associating to network
[03:14:38][W][wifi:1659]: Connecting to network failed (callback)
[03:14:38][D][wifi:1838]: Retry phase: INITIAL_CONNECT → SCAN_CONNECTING
[03:14:38][D][wifi:1289]: Starting scan
[03:14:38][I][app:231]: ESPHome version 2026.3.3 compiled on 2026-04-10 01:52:17 -0400
[03:14:38][I][app:238]: ESP32 Chip: ESP32-C6 rev0.2, 1 core(s)
[03:14:38][C][logger:229]: Logger:
[03:14:38][C][logger:229]:  Max Level: DEBUG
[03:14:38][C][logger:229]:  Initial Level: DEBUG
[03:14:38][C][logger:236]:   Log Baud Rate: 115200
[03:14:38][C][logger:236]:  Hardware UART: USB_SERIAL_JTAG
[03:14:38][C][logger:246]:   Task Log Buffer Size: 768 bytes
[03:14:38][C][i2c.idf:092]: I2C Bus:
[03:14:38][C][i2c.idf:093]:   SDA Pin: GPIO6
[03:14:38][C][i2c.idf:093]:  SCL Pin: GPIO7
[03:14:38][C][i2c.idf:093]:  Frequency: 100000 Hz
[03:14:38][C][i2c.idf:099]:   Timeout: 13000us
[03:14:38][C][i2c.idf:103]:   Recovery: bus successfully recovered
[03:14:38][C][template.binary_sensor:016]: Template Binary Sensor 'Fall Motion Detected'
[03:14:38][C][template.binary_sensor:237]:   Device Class: 'motion'
[03:14:38][C][template.binary_sensor:016]: Template Binary Sensor 'Battery Low Alert'
[03:14:38][C][template.binary_sensor:237]:   Device Class: 'battery'
[03:14:38][C][template.binary_sensor:016]: Template Binary Sensor 'Person Still After Fall'
[03:14:38][C][template.binary_sensor:237]:   Device Class: 'safety'
[03:14:38][C][template.binary_sensor:016]: Template Binary Sensor 'I2C Error'
[03:14:38][C][template.binary_sensor:237]:   Device Class: 'problem'
[03:14:38][C][template.sensor:017]: Template Sensor 'Battery Capacity'
[03:14:38][C][template.sensor:017]:  State Class: ''
[03:14:38][C][template.sensor:017]:  Unit of Measurement: '%'
[03:14:38][C][template.sensor:017]:  Accuracy Decimals: 0
[03:14:38][C][template.sensor:237]:   Device Class: 'battery'
[03:14:38][C][template.sensor:449]:   Update Interval: never
[03:14:38][C][template.text_sensor:016]: Template Sensor 'Wakeup Reason'
[03:14:38][C][template.text_sensor:016]: Template Sensor 'Fall Detector Heartbeat'
[03:14:38][C][template.text_sensor:228]:   Icon: 'mdi:information-box'
[03:14:38][C][gpio.output:010]: Binary Output:
[03:14:38][C][gpio.output:152]:   Pin: GPIO15
[03:14:38][C][switch.gpio:091]: GPIO Switch 'BMI160 Power Switch'
[03:14:38][C][switch.gpio:091]:  Restore Mode: always ON
[03:14:38][C][switch.gpio:152]:   Pin: GPIO5
[03:14:38][C][template.number:016]: Template Number 'High G Sensitivity (G)'
[03:14:38][C][template.number:228]:   Icon: 'mdi:gauge'
[03:14:38][C][template.number:243]:   Unit of Measurement: 'G'
[03:14:38][C][template.number:049]:   Optimistic: YES
[03:14:38][C][template.number:453]:   Update Interval: 60.0s
[03:14:38][C][template.number:016]: Template Number 'Low-G Duration (ms)'
[03:14:38][C][template.number:243]:   Unit of Measurement: 'ms'
[03:14:38][C][template.number:049]:   Optimistic: YES
[03:14:38][C][template.number:453]:   Update Interval: 60.0s
[03:14:38][C][template.number:016]: Template Number 'Low-G Threshold (mg)'
[03:14:38][C][template.number:243]:   Unit of Measurement: 'mg'
[03:14:38][C][template.number:049]:   Optimistic: YES
[03:14:38][C][template.number:453]:   Update Interval: 60.0s
[03:14:38][C][template.number:016]: Template Number 'High-G Duration (ms)'
[03:14:38][C][template.number:243]:   Unit of Measurement: 'ms'
[03:14:38][C][template.number:049]:   Optimistic: YES
[03:14:38][C][template.number:453]:   Update Interval: 60.0s
[03:14:38][C][template.number:016]: Template Number 'High-G Hysteresis'
[03:14:38][C][template.number:049]:   Optimistic: YES
[03:14:38][C][template.number:453]:   Update Interval: 60.0s
[03:14:38][C][template.number:016]: Template Number 'Low-G Hysteresis'
[03:14:38][C][template.number:049]:   Optimistic: YES
[03:14:38][C][template.number:453]:   Update Interval: 60.0s
[03:14:38][C][homeassistant.time:010]: Home Assistant Time
[03:14:39][C][time:049]: Timezone: UTC-5:00 (DST UTC-4:00)
[03:14:39][C][time:055]: Current time: 2026-04-10 04:24:23
[03:14:39][C][i2c_device:012]: I2CDevice
[03:14:39][C][i2c_device:013]:   Address: 0x68
[03:14:39][C][adc.esp32:017]: ADC Sensor 'Battery Voltage'
[03:14:39][C][adc.esp32:017]:  State Class: 'measurement'
[03:14:39][C][adc.esp32:017]:  Unit of Measurement: 'V'
[03:14:39][C][adc.esp32:017]:  Accuracy Decimals: 2
[03:14:39][C][adc.esp32:237]:   Device Class: 'voltage'
[03:14:39][C][adc.esp32:152]:   Pin: GPIO0
[03:14:39][C][adc.esp32:124]:   Channel:       0
[03:14:39][C][adc.esp32:124]:  Unit:          ADC1
[03:14:39][C][adc.esp32:124]:  Attenuation:   12 dB
[03:14:39][C][adc.esp32:124]:  Samples:       1
[03:14:39][C][adc.esp32:124]:  Sampling mode: average
[03:14:39][C][adc.esp32:124]:  Setup Status:
[03:14:39][C][adc.esp32:124]:    Handle Init:  OK
[03:14:39][C][adc.esp32:124]:    Config:       OK
[03:14:39][C][adc.esp32:124]:    Calibration:  OK
[03:14:39][C][adc.esp32:124]:    Overall Init: OK
[03:14:39][C][adc.esp32:453]:   Update Interval: 60.0s
[03:14:39][C][restart.button:014]: Restart Button 'Restart Beetle'
[03:14:39][C][restart.button:228]:   Icon: 'mdi:restart'
[03:14:39][C][captive_portal:134]: Captive Portal:
[03:14:39][C][wifi:1517]: WiFi:
[03:14:39][C][wifi:1517]:  Local MAC: FC:01:2C:EC:51:30
[03:14:39][C][wifi:1517]:  Connected: NO
[03:14:39][C][esphome.ota:071]: Over-The-Air updates:
[03:14:39][C][esphome.ota:071]:  Address: fall-detector-mamma.local:3232
[03:14:39][C][esphome.ota:071]:  Version: 2
[03:14:39][C][esphome.ota:078]:   Password configured
[03:14:39][C][web_server.ota:238]: Web Server OTA
[03:14:39][C][api:237]: Server:
[03:14:39][C][api:237]:  Address: fall-detector-mamma.local:6053
[03:14:39][C][api:237]:  Listen backlog: 4
[03:14:39][C][api:237]:  Max connections: 8
[03:14:39][C][api:244]:   Noise encryption: YES
[03:14:39][C][homeassistant.binary_sensor:016]: Homeassistant Binary Sensor 'service_mode_switch'
[03:14:39][C][homeassistant.binary_sensor:039]:   Entity ID: 'input_boolean.fall_detector_service_mode'
[03:14:39][C][wifi_signal.sensor:017]: WiFi Signal 'WiFi Signal Strength'
[03:14:39][C][wifi_signal.sensor:017]:  State Class: 'measurement'
[03:14:39][C][wifi_signal.sensor:017]:  Unit of Measurement: 'dBm'
[03:14:39][C][wifi_signal.sensor:017]:  Accuracy Decimals: 0
[03:14:39][C][wifi_signal.sensor:237]:   Device Class: 'signal_strength'
[03:14:39][C][mdns:194]: mDNS:
[03:14:39][C][mdns:194]:  Hostname: fall-detector-mamma
[03:14:39][C][deep_sleep:027]: Deep sleep:
[03:14:39][C][deep_sleep:030]:   Sleep Duration: 600000 ms
[03:14:39][C][deep_sleep:033]:   Run Duration: 60000 ms
[03:14:39][C][deep_sleep:152]:   Wakeup Pin: GPIO4
[03:14:40][I][BMI160:967]: Ready! 100Hz System Armed.
[03:14:40][W][component:520]: esphome.coroutine took a long time for an operation (501 ms), max is 30 ms
[03:14:40][W][component:520]: template.binary_sensor took a long time for an operation (502 ms), max is 30 ms
[03:14:41][D][text_sensor:120]: 'Fall Detector Heartbeat' >> 'Fri 2026-04-10 04:24:25'
[03:14:41][D][wifi:1449]: Found networks:
[03:14:41][I][wifi:1420]: - 'ace-2.4' [redacted]▂▄▆█ Ch: 6 -53dB P:0
[03:14:41][I][wifi:1420]: - 'ace-2.4' [redacted]▂▄▆█ Ch: 6 -59dB P:0
[03:14:41][I][wifi:1088]: Connecting to [redacted] [redacted] (priority 0, attempt 1/2 in phase SCAN_CONNECTING)...
[03:14:42][D][sensor:124]: 'Battery Voltage' >> 4.14 V
[03:14:42][D][sensor:124]: 'Battery Capacity' >> 100 %
[03:14:42][I][wifi:1558]: Connected
[03:14:42][D][wifi:1575]: Disabling AP
[03:14:42][C][wifi:1228]:   IP Address: 192.168.2.173
[03:14:42][C][wifi:1239]:   SSID: [redacted]
[03:14:42][C][wifi:1239]:  BSSID: [redacted]
[03:14:42][C][wifi:1239]:  Hostname: 'fall-detector-mamma'
[03:14:42][C][wifi:1239]:  Signal strength: -54 dB ▂▄▆█
[03:14:42][C][wifi:1239]:  Channel: 6
[03:14:42][C][wifi:1239]:  Subnet: 255.255.255.0
[03:14:42][C][wifi:1239]:  Gateway: 192.168.2.2
[03:14:42][C][wifi:1239]:  DNS1: 192.168.2.2
[03:14:42][C][wifi:1239]:  DNS2: 192.168.2.2
[03:14:42][D][sensor:124]: 'WiFi Signal Strength' >> -54 dBm
[03:14:42][W][component:429]: wifi cleared Warning flag
[03:14:42][D][main:1027]: Check-in complete. Entering sleep timer.
[03:14:47][D][binary_sensor:048]: 'Battery Low Alert' >> OFF
[03:14:47][D][main:089]: Switch LED to Healthy on boot
[03:14:48][D][text_sensor:120]: 'Wakeup Reason' >> 'Power On / Other (0)'
[03:14:48][D][sensor:124]: 'WiFi Signal Strength' >> -54 dBm
[03:14:48][D][text_sensor:120]: 'Fall Detector Heartbeat' >> 'Fri 2026-04-10 04:24:32'
[03:15:17][D][api:222]: Accept 192.168.2.118
[03:15:17][W][component:429]: api cleared Warning flag
[03:15:17][D][api.connection:2440]: Home Assistant 2026.3.3 (192.168.2.118): connected
[03:15:17][D][time:104]: Synchronized time: 2026-04-10 03:15:17
[03:15:18][D][homeassistant.binary_sensor:025]: 'input_boolean.fall_detector_service_mode': Got state ON
[03:15:18][D][binary_sensor:048]: 'service_mode_switch' >> ON
[03:15:20][D][main:308]: Maintenance Mode ON - Sleep Disabled
[03:15:42][D][sensor:124]: 'Battery Voltage' >> 4.14 V
[03:15:42][D][sensor:124]: 'Battery Capacity' >> 100 %

yaml part 1 for this project

globals:
  - id: led_mode
    type: int
    initial_value: '1' # 1=Healthy, 2=Battery Low, 3=Emergency

substitutions:
  dozetime: "10min"
  runtime: "60s"
  battlow: "3.7"
  downtime: 120000
  falldelay: "60s"
  bitsperG: "4096.0"
  fallhold: "50"  # If we reach 50 samples (50 * 200ms = 10.0s)
  High5GTH: "0x50"  # 2.5G threshold
                    # default: 0b11000000 = 1500mg,  [int_high_th]*7.81mg (2g range), *15.63(4g), *31.25(8g), *62.5(16g)
  default_high_g: "2.5" # Start at 2.5G

esphome:
  name: "fall-detector-mamma"
  includes:
    - "<driver/gpio.h>"
    #- "<driver/i2c_master.h>"  # Required for i2c_master_bus_reset
    #- "<driver/i2c.h>"  # Required for i2c_master_bus_reset

  #platformio_options: 
  #  build_flags:
  #    - -DCONFIG_BT_ENABLED=n
  on_boot:
    - priority: 900  # Highest priority to run BEFORE the switch component
      then:
        - lambda: |-
            // 1. Manually set the pin to OUTPUT and LOW (ON)
            // this prepares the hardware state before we release the "hold"
            gpio_set_direction(GPIO_NUM_5, GPIO_MODE_OUTPUT);
            gpio_set_level(GPIO_NUM_5, 0);          
            // 2. Now safely release the hold. 
            // Since the pin is already driven LOW by the code above, 
            // the MOSFET stays ON without a flicker.
            gpio_hold_dis(GPIO_NUM_5);

    - priority: 600
      then:     
      - delay: 2s # Let power settle
      - script.execute: soft_reset_sensor      
      - delay: 2s # Give the state a moment to sync
      - logger.log: "Check-in complete. Entering sleep timer."
      - delay: 5s # give time for system to talk to wifi
      - if:
          condition: { lambda: 'return id(battery_voltage).state <= ${battlow};' }
          then:
            - binary_sensor.template.publish:
                id: battery_low
                state: ON
            - if:
                condition: { lambda: 'return !id(confirmed_fall).state && !id(hw_pin).state;' }
                then:
                  - lambda: 'id(led_mode) = 2;'
                  - logger.log: "Switch LED to Battery low on boot"
                else:
                  - lambda: 'id(led_mode) = 3;'
                  - logger.log: "Switch LED to Emergency"              
          else:
            - binary_sensor.template.publish:
                id: battery_low
                state: OFF
            - if:
                condition: { lambda: 'return !id(confirmed_fall).state && !id(hw_pin).state;' }
                then:
                  - lambda: 'id(led_mode) = 1;'
                  - logger.log: "Switch LED to Healthy on boot"
                else:
                  - lambda: 'id(led_mode) = 3;'
                  - logger.log: "Switch LED to Emergency"    
      - component.update: wakeup_reason_sensor
      - component.update: wifi_rssi
      - text_sensor.template.publish:
          id: heartbeat_pulse
          state: !lambda 'return {id(homeassistant_time).now().strftime("%a %Y-%m-%d %H:%M:%S")};'      

  on_shutdown:
    priority: 900
    then:
      - lambda: |-
          // 1. Ensure pin is driven LOW (MOSFET ON)
          gpio_set_level(GPIO_NUM_5, 0);
          // 2. Enable the hold function for this specific pin
          // On C6, this persist through deep sleep automatically
          gpio_hold_en(GPIO_NUM_5);


esp32:
  board: esp32-c6-devkitc-1
#  cpu_frequency: 80MHz # Lowers draw by ~15mA
  framework:
    type: esp-idf
    sdkconfig_options:
      CONFIG_ESP_TASK_WDT_EN: "y"
      CONFIG_ESP_TASK_WDT_PANIC: "y"
      CONFIG_ESP_TASK_WDT_TIMEOUT_S: "20"
      CONFIG_RTC_CLK_SRC_INT_RC: "y"

# This is the "Safety" fix - prevents the background task crash
      #CONFIG_PM_ENABLE: "n"
      #CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE: n
      #CONFIG_ESP_INT_WDT_TIMEOUT_MS: "1000"
# NEW: This can sometimes help with C6 radio stability
      #CONFIG_ESP_PHY_ENABLE_USB: n
#      CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG: n
#      CONFIG_ESP_CONSOLE_UART_DEFAULT: y


# Enable logging
logger:
    level: DEBUG

# Completely disable the safety mode to save battery on Deep Sleep reboots
safe_mode:
  disabled: True

# Enable Home Assistant API
api:
  id: api_server
  encryption:
    key: "*"

time:
  - platform: homeassistant
    id: homeassistant_time

ota:
  - platform: esphome
    password: "*"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  #power_save_mode: NONE
  fast_connect: true  # Skips full channel scan
  #post_connect_roaming: false # <--- Stops the "Roam scan" and its CPU hit
  #output_power: 19dBm # Slightly lowering power can prevent voltage dips on the Beetle
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Fall-Detector-Mamma"
    password: "*"

captive_portal:

# 1. Hardware Wakeup (The only place GPIO4 is declared)
deep_sleep:
  id: sleep_mode
  wakeup_pin: 
    number: GPIO4
    inverted: true
    mode: INPUT_PULLUP
  run_duration: ${runtime} 
  sleep_duration: ${dozetime}

# 2. Virtual Sensor for Home Assistant
binary_sensor:
  # Use a template instead of 'platform: gpio' to avoid the pin conflict
  - platform: template
    name: "Fall Motion Detected"
    id: hw_pin
    device_class: motion
    trigger_on_initial_state: true  # Ensures first 'true' is caught
    # Reset to OFF 5 seconds after the last shake/motion detected
    #filters:
    #  - delayed_off: 30s 
    on_press:
      then:
        # Flash continuously during the emergency wake period
        - lambda: 'id(led_mode) = 3;' # Switch to Emergency
        - logger.log: "Switch LED to emergency"
        - logger.log: "Preventing Sleep mode due to Fall"
        - logger.log: "FALL Detected!"
        - deep_sleep.prevent: 
            id: sleep_mode

  # Service Mode Switch (from Home Assistant)
  - platform: homeassistant
    id: service_mode_switch
    entity_id: input_boolean.fall_detector_service_mode
    trigger_on_initial_state: true  # ensures state is caught at first boot
    on_press:
      then:
        - delay: 2s
        - deep_sleep.prevent: 
            id: sleep_mode
        - logger.log: "Maintenance Mode ON - Sleep Disabled"
    on_release:
      then:
        - delay: 2s
        - logger.log: "Maintenance Mode OFF - Returning to sleep schedule except if Fall detected"
        # This force-starts the countdown for sleep
        - if:
            condition: { lambda: 'return !id(confirmed_fall).state && !id(hw_pin).state;' }
            then:
              - logger.log: "Maintenance Mode OFF -Allowing sleep"
              - deep_sleep.allow:
                  id: sleep_mode
                  #sleep_duration: ${dozetime} 

  - platform: template
    name: "Battery Low Alert"
    id: battery_low
    device_class: battery

  # Combines Motion + Orientation + 3s stability filter
  - platform: template
    name: "Person Still After Fall"
    id: confirmed_fall
    device_class: safety
    trigger_on_initial_state: true  # Ensures first 'true' is caught
    on_press:
      then:
        # Flash continuously during the emergency wake period
        - lambda: 'id(led_mode) = 3;' # Switch to Emergency
        - logger.log: "Switch LED to emergency"
        - logger.log: "Preventing Sleep mode due to Confirmed Fall"
        - logger.log: "⚠️ FALL CONFIRMED! User is down and stationary. Holding wake..."
        - deep_sleep.prevent: 
            id: sleep_mode

    on_release:
      then:
        - if:
            condition: { lambda: 'return (id(battery_voltage).state <= ${battlow}) && !id(hw_pin).state;' }
            then:
              - lambda: 'id(led_mode) = 2;' # Switch to battery low
              - logger.log: "Switch LED to battery low afer emergency cleared"
              - logger.log: "✅ Fall Alert Cleared - Device moved or timer expired"
            else:
              - if:
                  condition: { lambda: 'return !id(hw_pin).state;' }
                  then:   
                  - lambda: 'id(led_mode) = 1;' # Switch to healthy
                  - logger.log: "Switch LED to healthy afer emergency cleared"
                  - logger.log: "✅ Fall Alert Cleared - Device moved or timer expired"



  - platform: template
    name: "I2C Error"
    id: i2c_error
    device_class: problem
    lambda: 'return id(i2c_error).state;' 

    # This ensures it shows up as "problem" in HA


# Define the BMI160 as a generic device
i2c_device:
  id: my_bmi160
  address: 0x68

i2c:
  - id: bus_a
    sda: GPIO6  # LP_SDA
    scl: GPIO7  # LP_SCL
    scan: false
#    setup_priority: -100 # stability patch
    frequency: 100khz
    timeout: 50ms
      

sensor:
  - platform: adc
    pin: GPIO0
    name: "Battery Voltage"
    id: battery_voltage
    attenuation: 12db
    update_interval: 60s
    accuracy_decimals: 2
    samples: 1
    filters:
      - lambda: |-
          // Tiny 10ms pause to ensure radio tasks have yielded
          delay(10); 
          return x;
      - multiply: 2.0 # The on-board voltage divider halves the voltage

    unit_of_measurement: "V"
    device_class: voltage
    state_class: measurement
    on_value:
      then:
        - component.update: battery_capacity

  - platform: template
    name: "Battery Capacity"
    id: battery_capacity
    unit_of_measurement: "%"
    device_class: battery
    update_interval: never
    accuracy_decimals: 0
    lambda: |-
      // Tiny 10ms pause to ensure radio tasks have yielded
      delay(10); 
      float v = id(battery_voltage).state;
      // 1. Full Charge / Surface Charge (Adjusted to your 4.1V resting reality)
      if (v >= 4.1) return 100.0;
      // 2. The Upper Plateau (4.1V down to 3.8V)
      // Map 3.9V-4.1V to 60%-100%
      if (v >= 3.80) return (v - 3.80) / (4.1 - 3.80) * 40.0 + 60.0;
      // 3. The Long Middle (3.9V down to 3.7V)
      // Map 3.7V-3.9V to 40%-60%
      if (v >= 3.70) return (v - 3.70) / (3.80 - 3.70) * 20.0 + 40.0;
      // 4. The "Near Empty" Warning (3.7V down to 3.5V)
      // Map 3.5V-3.7V to 0%-40%
      if (v >= 3.50) return (v - 3.50) / (3.70 - 3.50) * 40.0;
      // 5. Critical / Cutoff
      return 0.0;

  - platform: wifi_signal
    name: "WiFi Signal Strength"
    id: wifi_rssi
    update_interval: 63s
    entity_category: diagnostic


text_sensor:
  - platform: template
    name: "Wakeup Reason"
    id: wakeup_reason_sensor
    update_interval: never
    lambda: |-
      int cause = esp_sleep_get_wakeup_cause();
      if (cause == 7) {
        id(sleep_mode).prevent_deep_sleep();
        return {"Motion (GPIO4)"};
      } else if (cause == 4) {
        return {"Scheduled Timer"};
      } else {
        return {"Power On / Other (" + std::to_string(cause) + ")"};
      }

  - platform: template
    name: "Fall Detector Heartbeat"
    id: heartbeat_pulse
    update_interval: 64s
    entity_category: "diagnostic"
    icon: "mdi:information-box"
    #lambda: 'return {id(homeassistant_time).now().strftime("%a %H:%M")};'
    lambda: 'return {id(homeassistant_time).now().strftime("%a %Y-%m-%d %H:%M:%S")};' 


output:
  - platform: gpio
    pin: 
      number: GPIO15
      ignore_strapping_warning: true 
    id: beetle_led_pin

switch:
  - platform: gpio
    pin: 
      number: GPIO5
      inverted: true
      # This helps ensure the pin is set correctly as soon as the driver loads
      mode: 
        output: true
    name: "BMI160 Power Switch"  # <--- ADD THIS LINE
    id: bmi160_power
    # "ALWAYS_ON" tells ESPHome to turn this switch ON (GPIO LOW) 
    # immediately upon boot-up.
    restore_mode: ALWAYS_ON
    on_turn_off: 
      then:          
      - lambda: |-
          // 1. Manually "detach" the I2C peripheral from the pins
          // effectively setting them back to high-impedance/GPIO mode
          gpio_reset_pin(GPIO_NUM_6); 
          gpio_reset_pin(GPIO_NUM_7);

          // 2. Force them LOW to prevent parasitic powering
          gpio_set_direction(GPIO_NUM_6, GPIO_MODE_OUTPUT); // SCL
          gpio_set_level(GPIO_NUM_6, 0);
          gpio_set_direction(GPIO_NUM_7, GPIO_MODE_OUTPUT); // SDA
          gpio_set_level(GPIO_NUM_7, 0);
          ESP_LOGI("main", "I2C Pins zeroed, BMI160 isolated.");

button:
  - platform: restart
    name: "Restart Beetle"
    id: beetle_restart

number:
  - platform: template
    name: "High G Sensitivity (G)"
    id: high_g_sensitivity
    icon: "mdi:gauge"
    min_value: 1.0
    max_value: 7.5
    step: 0.1
    initial_value: ${default_high_g}
    optimistic: true
    restore_value: true
    unit_of_measurement: "G"
    mode: slider
    on_value:
      then:
        - lambda: |-
            // The BMI160 High-G threshold register (0x5E) works in increments.
            // In 8G range, each bit is 31.25mg. 
            // Formula: Raw Byte = (Desired G * 1000) / 31.25
            uint8_t threshold_byte = (uint8_t)(x * 1000.0f / 31.25f);
            
            auto *sensor = id(my_bmi160);
            sensor->write_byte(0x5E, threshold_byte);
            ESP_LOGI("BMI160", "Sensitivity updated to %.1fG (Byte: 0x%02X)", x, threshold_byte);

  # 0x5A: Low-G Duration (Time the sensor must be "weightless" to trigger)
  - platform: template
    name: "Low-G Duration (ms)"
    id: low_g_duration
    min_value: 2.5
    max_value: 640
    step: 2.5
    initial_value: 200 # Your current 0x4F
    optimistic: true
    restore_value: true
    unit_of_measurement: "ms"
    on_value:
      then:
        - lambda: 'id(my_bmi160)->write_byte(0x5A, (uint8_t)((x / 2.5f) - 1));'

  # 0x5B: Low-G Threshold (The "weightlessness" limit, usually < 0.5G)
  - platform: template
    name: "Low-G Threshold (mg)"
    id: low_g_threshold
    min_value: 7.8
    max_value: 1000
    step: 7.8
    initial_value: 250 # Your current 0x20
    optimistic: true
    restore_value: true
    unit_of_measurement: "mg"
    on_value:
      then:
        - lambda: 'id(my_bmi160)->write_byte(0x5B, (uint8_t)(x / 7.81f));'

  # 0x5D: High-G Duration (How long the impact must last)
  - platform: template
    name: "High-G Duration (ms)"
    id: high_g_duration
    min_value: 2.5
    max_value: 640
    step: 2.5
    initial_value: 100 # Your current 0x27
    optimistic: true
    restore_value: true
    unit_of_measurement: "ms"
    on_value:
      then:
        - lambda: 'id(my_bmi160)->write_byte(0x5D, (uint8_t)((x / 2.5f) - 1));'

  # 0x5C: Hysteresis (Split into two sliders because they share one register)
  - platform: template
    name: "High-G Hysteresis"
    id: high_g_hysteresis
    min_value: 0
    max_value: 3
    step: 1
    initial_value: 2
    optimistic: true
    on_value:
      then:
        - lambda: |-
            uint8_t val;
            id(my_bmi160)->read_byte(0x5C, &val);
            val = (val & 0x3F) | ((uint8_t)x << 6); // Update only bits 7-6
            id(my_bmi160)->write_byte(0x5C, val);

  - platform: template
    name: "Low-G Hysteresis"
    id: low_g_hysteresis
    min_value: 0
    max_value: 3
    step: 1
    initial_value: 1
    optimistic: true
    on_value:
      then:
        - lambda: |-
            uint8_t val;
            id(my_bmi160)->read_byte(0x5C, &val);
            val = (val & 0xFC) | ((uint8_t)x); // Update only bits 1-0
            id(my_bmi160)->write_byte(0x5C, val);

yaml part2

interval:
  - interval: 200ms
    then:
      - lambda: |-
          //static uint32_t last_error_log = 0; // Declare this first
          uint8_t status_any;
          uint8_t status_high_g;
          uint8_t status_sign;
          uint8_t status_sign2;
          static uint32_t cause2 = 0;

          uint8_t data[6];  // Buffer for X, Y, and Z axes (2 bytes each)
          static uint32_t flat_samples = 0; // The 3-second counter
          static uint32_t counter = 0;
          static uint32_t last_motion_time = 0;
          //static uint8_t error_events = 0;
          //static uint8_t reboot_attempts = 0;

          //uint32_t now = millis();
          counter++;

          // Register 0x1C
          // Interrupt Status (byte0)
          // Bit 7 flat_int 
          // Bit 6 orient_int
          // Bit 5 s_tap_int
          // Bit 4 d_tap_int
          // Bit 3 pmu_trigger_int
          // Bit 2 anym_int
          // Bit 1 sigmot_int
          // Bit 0 Step_int
          // status_any: To get double tap and anymotion interrupts: 0b00010100, 0x14         

          // Register 0x1D
          // Interrupt Status (byte1)
          // Bit 7 nomo_int 
          // Bit 6 fwm_int
          // Bit 5 ffull_int
          // Bit 4 drdy_int
          // Bit 3 lowg_int
          // Bit 2 highg_z_int
          // Bit 1 reserved
          // Bit 0 reserved
          // status_high_g: To get lowg and highg_z interrupts: 0b00001100, 0x0C  

          // Register 0x1E
          // Interrupt Status Additional info (byte2)
          // Bit 7 tap_sign, sign of single/double tap triggering signal was 0: positive, 1: negative 
          // Bit 6 tap_first_z, triggered (1) by z-axis
          // Bit 5 tap_first_y, triggered (1) by y-axis
          // Bit 4 tap_first_x, triggered (1) by x-axis
          // Bit 3 anym_sign, slope sign of slope tap triggering signals was 0: positive, 1: negative
          // Bit 2 anym_first_z, triggered (1) by z-axis
          // Bit 1 anym_first_y, triggered (1) by y-axis
          // Bit 0 anym_first_y, triggered (1) by x-axis
          // status_sign: To get x,y,z trigger info: 0b11111111, 0xFF 

          // Register 0x1F
          // Interrupt Status Additional info (byte3)
          // Bit 7 flat, flat (1) 
          // Bit 6 orient<2>, upward looking (0), downward looking (1)
          // Bit 5-4 orient<1:0>, orientation of xx-y plane: 00: portrait upright, 11: portrait upside down, 10: landscape left, 11: landscape right
          // Bit 3 high_sign, slope sign of acceleration signal that triggered high-g interrupt was 0: positive, 1: negative
          // Bit 2 high_first_z, triggered (1) by z-axis
          // Bit 1 high_first_y, triggered (1) by y-axis
          // Bit 0 high_first_x, triggered (1) by x-axis
          // status_sign2: To get x,y,z trigger and high sign info: 0b00001111, 0x0F 

          // 1. Check for NEW motion
          bool success1 = id(my_bmi160).read_byte(0x1C, &status_any);
          bool success2 = id(my_bmi160).read_byte(0x1D, &status_high_g);
          bool success3 = id(my_bmi160).read_byte(0x1E, &status_sign);
          bool success4 = id(my_bmi160).read_byte(0x1F, &status_sign2);
          //ESP_LOGI("BMI160", "[cause2:%i]", cause2);
          if (cause2 != 99) {cause2 = esp_sleep_get_wakeup_cause(); }
          if (success1 && success2 && success3 && success4) {
          // Filter for lowg, hight and status_high_g, and anymotion on status_any
          //    if ((status_high_g & 0x0C) || (status_any & 0x04) || cause2 == 7) {
              if ((status_high_g & 0x0C) || cause2 == 7) {
                  cause2 = 99;  
                  last_motion_time = millis(); // Record the timestamp
                  //ESP_LOGI("main", "HW latch is on (GPIO4)");
                  ESP_LOGI("main", "FALL DETECTED!");
                  id(sleep_mode).prevent_deep_sleep();
                  //ESP_LOGI("BMI160", "status_any:%u, status_high_g:%u, status_sign:%u, status_sign2:%u",status_any,status_high_g,status_sign,status_sign2);
                    ESP_LOGI("BMI160", "Status [Any:0x%02X] [HighG:0x%02X] [Sign:0x%02X] [Sign2:0x%02X] [cause2:%i]", status_any, status_high_g, status_sign, status_sign2, cause2);

                  if (!id(hw_pin).state || !id(hw_pin).has_state()) {id(hw_pin).publish_state(true);}
                  if (id(i2c_error).state || !id(i2c_error).has_state()) {
                    id(i2c_error).publish_state(false);
                    ESP_LOGI("main", "Resetting I2C Error to False");
                  }
              } else {
                if (!id(hw_pin).has_state()) {
                  id(hw_pin).publish_state(false);
                  id(confirmed_fall).publish_state(false);
                  ESP_LOGI("main", "Resetting fall sensors");
                }
              }
          } else {
              id(error_check).execute(); // check for error      
          }

          // 2. The Smart 30-second "Self-Reset" Logic
          // Reset ONLY if: 
          // - It's currently ON
          // - AND there is no active 'Confirmed Fall'
          // - AND the downtime has passed
          //if (id(hw_pin).state && !id(confirmed_fall).state && (millis() - last_motion_time > ${downtime})) {
          //  ESP_LOGI("main", "Motion timeout: Resetting hw_pin to False");
          //  id(hw_pin).publish_state(false);
          //}
          // 3. Process Gravity for Fall Validation
          if (id(my_bmi160).read_bytes(0x12, data, 6)) {
          // BMI160 sends Little Endian: [0]=ACC_X_LSB, [1]=ACC_X_MSB [2]=ACC_Y_LSB [3]=ACC_Y_MSB [4]=ACC_Z_LSB, [5]=ACC_Z_MSB
            int16_t raw_x = (int16_t)((uint16_t)data[0] | ((uint16_t)data[1] << 8));
            int16_t raw_y = (int16_t)((uint16_t)data[2] | ((uint16_t)data[3] << 8));
            int16_t raw_z = (int16_t)((uint16_t)data[4] | ((uint16_t)data[5] << 8));

            float gx = (float)raw_x / ${bitsperG};
            float gy = (float)raw_y / ${bitsperG};
            float gz = (float)raw_z / ${bitsperG};

            // 2. Calculate Total Vector Magnitude
            float magnitude = sqrt(gx*gx + gy*gy + gz*gz);

            if (id(i2c_error).state || !id(i2c_error).has_state()) {
              id(i2c_error).publish_state(false);
              ESP_LOGI("main", "Resetting I2C Error to False");
            }

            // Log the G-Force to the debug console
            //ESP_LOGI("main", "Current Z-axis: %.2fg", g_z);
            
            // Fall Validation Logic (Widened for real-world scenarios)
            // Checks if Z-axis is roughly between 0.7g and 1.3g (lying flat-ish)
            // AND the Motion sensor (hw_pin) was triggered by the impact.
            // Validation: Impact (hw_pin) + Orientation (0.7-1.3G)
            // if (id(hw_pin).state && (g_z > 0.7 && g_z < 1.3)) {

            // Fall Validation Logic
            // We check if an impact occurred (hw_pin) AND the user is now stationary (0.80 - 1.20g)
            // This works even if they are lying on their side, back, or front.
            if (id(hw_pin).state && (magnitude > 0.80 && magnitude < 1.20)) {
              flat_samples++; // Add 200ms to the timer            
              //ESP_LOGI("main", "Orientation check: Person is FLAT (Waiting for 3s filter...)");
              // If we reach 50 samples (50 * 200ms = 10.0s)
              if (flat_samples >= ${fallhold}) {
                // Only log and publish if the sensor isn't ALREADY in the 'true' state
                if (id(confirmed_fall).state == false) {
                  ESP_LOGI("main", "⚠️ FALL CONFIRMED! (stillness threshold met) - in interval");
                  id(led_mode) = 3; // Switch to Emergency
                  id(confirmed_fall).publish_state(true);
                }
              }
            }
            // --- RESET LOGIC ---
            // If they move, tilt, or the motion flag clears, RESET the timer instantly 
            else {
              // If they move or tilt, RESET the timer instantly
              flat_samples = 0;
              if (id(confirmed_fall).state == true) {
                ESP_LOGI("main", "✅ FALL ALERT CLEARED (User moved or tilted)");
                if (id(battery_voltage).state <= ${battlow}) {
                  id(led_mode) = 2; // Switch to Battery Low
                } else {
                  id(led_mode) = 1; // Switch to Healthy
                }
                id(confirmed_fall).publish_state(false);
              }
            }             
          } else {
            // Error Handling
            id(error_check).execute(); // check for error      
          }

          // check if we are not in service mode, and then put device to sleep depending on fall detection
          if (id(service_mode_switch).has_state() && !id(service_mode_switch).state) {
            if ((id(hw_pin).state && id(hw_pin).has_state())  && (millis() - last_motion_time > ${downtime})) {
              last_motion_time = millis(); // prevent from repeated settings
              id(sleep_mode).allow_deep_sleep();
            }
          }

          if (id(led_mode) == 3) { // EMERGENCY: Fast Strobe
            id(beetle_led_pin).set_state(counter % 2 == 0);
            //ESP_LOGI("main", "Emergency LED");
          } 
          else if (id(led_mode) == 2) { // BATT LOW: Double Pulse
            // A pattern that repeats every 2 seconds (10 ticks * 200ms)
            uint32_t pos = counter % 10;
            bool led_on = (pos == 0 || pos == 2); 
            id(beetle_led_pin).set_state(led_on);
            //ESP_LOGI("main", "Low Battery LED");

          }
          else if (id(led_mode) == 1) { // HEALTHY: Solid
            id(beetle_led_pin).set_state(true);
            //ESP_LOGI("main", "Healthy LED");

          }
          else { // OFF
            id(beetle_led_pin).set_state(false);
            //ESP_LOGI("main", "LED Off");

          }

script:
  - id: recover_i2c_bus
    mode: restart
    then:
 
      - switch.turn_off: bmi160_power
      - delay: 500ms
      - button.press: beetle_restart         
      - logger.log: "Power cycled BMI160."

  - id: soft_reset_sensor
    mode: restart
    then:
      - lambda: |-
          auto *sensor = id(my_bmi160);
          uint8_t dummy;
          
          // Reset and Power Up
          // Register 0x7E
          // Command register, triggers operations like softreset, NVM programming, etc
          sensor->write_byte(0x7E, 0xB6);
          delay(200);
          sensor->write_byte(0x7E, 0x11); // 0x11 Normal Mode, 0x12 Low Power mode
          delay(200);

          // Register 0x40 (reset:0xb00101000)
          // Sets the output data rate, bandwidth, and the read mode of the acceleration sensor
          // Bit 7 (acc_us) 0-Normal, 1-Low Power
          // Bit 6-4 (BWP) 2-Normal mode, 1-OSR2, 0-OSR4 (averaged samples = 2^(Val(acc_bwp)))
          // Bits 3-0 (ODR) = 8 (100hz)  -- 100/2^(8-val(odr))
          // Value 0x28 = Normal mode, Normal mode, 100Hz 0xb00101000
          //sensor->write_byte(0x40, 0x28);  
          //delay(100);

          // Register 0x41
          // Selection of the accelerometer g-range
          // Bit 7-4 Reserved
          // Bit 3-0 acc_range, default: 0x03 (2G)
          // Set Accel Range to 2G (Required for your 16384.0 calculation)
          // Set Accel Range to 8G (Required for your 4096 calculation)
          // Reg 0x41: 0x03 = 2G range, 0x05 - 4g range, 0x08 - 8g range, 0x0c - 16g
          sensor->write_byte(0x41, 0x08);
          delay(10);

          // Register 0x50          
          // Controls which interrupt engines are enabled (Reg 0x50) Value 0x17 = 0b00010111
          // anymotion engines, and double
          // Bit 7 int_flat_en  0-disabled, 1-enabled
          // Bit 6 int_orient_en 0-disabled, 1-enabled
          // Bit 5 int_s_tap_en 0-disabled, 1-enabled
          // Bit 4 int_d_tap_en 0-disabled, 1-enabled
          // Bit 3 reserved
          // Bit 2 int_anymo_z_en 0-disabled, 1-enabled
          // Bit 1 int_anymo_y_en 0-disabled, 1-enabled
          // Bit 0 int_anymo_y_en 0-disabled, 1-enabled
          // sensor->write_byte(0x50, 0x17); // any_motion xyz + double tap
          // sensor->write_byte(0x50, 0x07); // any_motion xyz
          // delay(10);

          // Register 0x51  
          // Controls which interrupt engines are enabled (Reg 0x51) Value 0x0F = 0b0001111
          // Enable Low G, and High-G on X, Y, Z axes (Reg 0x51)
          // Bit 0 High g x
          // Bit 1 High g y
          // Bit 2 High g z
          // Bit 3 Low g
          sensor->write_byte(0x51, 0x0F); // enabled Low G, and High G on x,y,z axes
          delay(10);

          // Register 0x53  
          // Contains the electrical definition of the interrupt pins
          // Register 0x53: INT1 Enabled, Push-Pull, Active-Low (Value 0x08)
          // Bit 7 int2_output_en  0-disabled, 1-enabled
          // Bit 6 int2_od 0-push-pull 1-open drain
          // Bit 5 int2_lvl 0-active low, 1-active high
          // Bit 4 int2_edge_ctrl 1-edged triggered 0-level (valid only when pin is input)
          // Bit 3 int1_output_en  0-disabled, 1-enabled
          // Bit 2 int1_od 0-push-pull 1-open drain
          // Bit 1 int1_lvl 0-active low, 1-active high
          // Bit 0 int1_edge_ctrl 1-edged triggered 0-level (valid only when pin is input)
          sensor->write_byte(0x53, 0x08); 
          delay(10);

          // Register 0x54 (default: 0b0000-0000)
          // Contains the interrupt reset bit, and the interrupt mode selection
          // Bit 7-6 reserved
          // Bit 5 Input enabled for INT2 pin, 0-disabled, 1-enabled
          // Bit 4 Input enabled for INT1 pin, 0-disabled, 1-enabled
          // Bit 3-0 Latched/non-latched/temporary interrupt modes
          //    0-non-latched, 1-312.5us, 2-625,3-1.25ms,4-2.5,5-5,6-10,7-20,8-40,9-80,10-160,11-320,12-640,13-1.28s,14-2.56s,15-latched
          // Set Latch to temporary 160ms (Value 0x0C -640ms)
          // This makes the pin pulse High for 640 milliseconds on motion
          sensor->write_byte(0x54, 0x0C); // 640 ms
          delay(10);

          // Register 0x55 (default: 0x00)
          // Control which interrupt signals are mapped to the INT1 (0x55/0x56) and INT2(0x56/0x57)  pins
          // Bit 7 flat  0-disabled, 1-enabled
          // Bit 6 orientation 0-disabled, 1-enabled
          // Bit 5 Single tap 0-disabled, 1-enabled
          // Bit 4 Double tap 0-disabled, 1-enabled
          // Bit 3 No Motion
          // Bit 2 Anymotion/Significant motion 0-disabled, 1-enabled
          // Bit 1 High-g 0-disabled, 1-enabled
          // Bit 0 Low-g/Step detection 0-disabled, 1-enabled         
          // Map Double tap (Bit4), Any-motion (Bit 2), AND High-G (Bit 1) to INT1 (Reg 0x55)
          // Value 0x16 = 0b00010110
          //sensor->write_byte(0x55, 0x16); 
          // sensor->write_byte(0x55, 0x06); // low G, High G, and anymotion
          sensor->write_byte(0x55, 0x03); // low G, High G
          delay(10);

          // Register 0x5A
          // Contains the delay time definition for the low-g interrupt
          // Bit 7-0 int_low_dur  2.5ms to 640ms, default: 0b00000111 = 20ms,  0x00=2.5ms, 0x01=5ms, ... [int_low_dur +1]*2.5         
          //sensor->write_byte(0x5A, 0x4F); // 79 (0x4F) --200ms
          sensor->write_byte(0x5A, (uint8_t)((id(low_g_duration).state / 2.5f) - 1));
          delay(10);

          // Register 0x5B
          // Contains the threshold definition for the low-g interrupt
          // Bit 7-0 int_low_thr  3.91mg to 2.000g, default: 0b00110000 (0x30) = 375mg,  [int_low_th]*7.81mg
          //sensor->write_byte(0x5B, 0x20); // 192 (0x20)) --250mg
          sensor->write_byte(0x5B, (uint8_t)(id(low_g_threshold).state / 7.81f));
          delay(10);

          // Register 0x5C
          // Contains high-g interrupt hysteresis setting, and the low-g interrupt hysteresis setting
          // Bit 7-6 int_high_hy  default: 2 (1G), int_high_hy*125mg (2G range), int_high_hy*250mg (4G), int_high_hy*500mg (8G), int_high_hy*1000mg (16G)
          // Bit 5-2 reserved
          // Bit 1-0 int_low_hy default: 1 (125mg), int_low_hy*125mg
          // sensor->write_byte(0x5C, 0xC1); // 3-1.5G for High G (8), and 1 (125mg) for low G
          uint8_t hyst = ((uint8_t)id(high_g_hysteresis).state << 6) | ((uint8_t)id(low_g_hysteresis).state);
          sensor->write_byte(0x5C, hyst);          
          delay(10);

          // Register 0x5D
          // Contains the delay time definition for the high-g interrupt
          // Bit 7-0 int_high_dur  2.5ms to 640ms, default: 0b00001011 = 30ms,  0x00=2.5ms, 0x01=5ms, ... [int_high_dur +1]*2.5
          //sensor->write_byte(0x5D, 0x27);
          sensor->write_byte(0x5D, (uint8_t)((id(high_g_duration).state / 2.5f) - 1)); 
          delay(10);

          // Register 0x5E
          // Contains the threshold definition for the high-g interrupt
          // Bit 7-0 int_high_thr  default: 0b11000000 = 1500mg,  [int_high_th]*7.81mg (2g range), *15.63(4g), *31.25(8g), *62.5(16g)
          // 0 = 3.91(2G), 7.81(4G), 15.63(8G), 31.25(16G)
          // 0xC0 = ~1.5G impact. Very "stiff," perfect for fall detection.
          // 0xF0 (~1.8G to 1.9G). This is near the limit for the 2G range.
          // It essentially requires the sensor to nearly "peg" its maximum reading to trigger. 
          // This will ignore almost any nudge, shake, or walking motion.  
          //sensor->write_byte(0x5E, 0xC0);   // 6G
          // sensor->write_byte(0x5E, ${High5GTH});   // 2G
          // Pull the current value from our slider instead of a substitution
          float current_g = id(high_g_sensitivity).state;
          sensor->write_byte(0x5E, (uint8_t)(current_g * 1000.0f / 31.25f));
          delay(10);

          // Register 0x5F (Default: 0x00)
          // Contains the definition of the number of samples to be evaluated for the anymotion interrupt and the slow/no-moition interrupt trigger delay
          // Bit 7-2 int_slo_no_mot_dur
          // Bit 1-0 int_anym_dur  # of samples + 1 to exceed threshold defined in 0x60
          // Any-motion Sensitivity (Real Fall Tuning)
          // Reg 0x5F: Any-motion Duration (Slope)
          // Bits [1:0] = 0x01 (2 samples). 
          // This requires the impact to last at least 2 readings (at 100Hz)
          // to prevent "static" or tiny vibrations from waking the Beetle.
          //sensor->write_byte(0x5F, 0x03); // 4 samples
          //delay(10);

          // Reg 0x60: Any-motion Threshold
          // Contains the threshold definition for the any-motion interrupt
          // Bit 7-0 int_anym_th default: 0b00010100 (0x14) -78mg,  int_anym_th*3.91mg (2G), *7.81mg(4G), *15.63mg(8G), *31.25mg(16G)
          // value of 0 = 1.95/3.91/7.81/15.63mg (2/4/8/16G)
          // Value 0x14 = ~78mg. (Lower = more sensitive, Higher = less sensitive)
          // Value 0x7F = ~500mg. (Lower = more sensitive, Higher = less sensitive)
          // 150mg is a good balance for detecting a body hitting the floor.
          // If you find the device wakes up "too easily" (false alarms just from walking), 
          //  0x7F (~500mg slope). To trigger this, you’d need a very violent shake or a significant physical jar that lasts for all 4 samples
          //sensor->write_byte(0x60, 0xFF); // 1000mg
          //delay(10);

          ESP_LOGI("BMI160", "Ready! 100Hz System Armed.");

  - id: error_check
    mode: restart
    then:
      - lambda: |-
          static uint32_t last_error_log = 0; // Declare this first
          static uint8_t error_events = 0;

          // Error Handling: Only log every 1 second (1000ms) to avoid flooding
          uint32_t now = millis();
          if (now - last_error_log > 1000) {
            ESP_LOGW("BMI160", "I2C Read Error - Check connections!");
            last_error_log = now;
            error_events++;
            ESP_LOGW("BMI160", "# of Error events is: %u", error_events);

            if (!id(i2c_error).state || !id(i2c_error).has_state()) {
              id(i2c_error).publish_state(true);
              ESP_LOGI("main", "Detected I2C Error");
            }

            

            if (error_events > 9) {
              ESP_LOGW("BMI160", "# of Error events is: %u", error_events);
              error_events=0;
              // only recover I2c and reboot beetle if not in a fall detected scenario to give time to report to HA
              // I2c bus will be recoverd on next timer wakeup
              if (!id(hw_pin).state || !id(hw_pin).has_state()) {
                id(recover_i2c_bus).execute(); // recover i2c bus
              }
              //reboot_attempts++;
            }
          }

I got rid of the panic, and now the system hangs after several hours running in awake mode. But then it recovers randomly, after 5 minutes, after 1 hour, etc. All the HA toggles and switches work, its just the main loop that seems to be stuck. I can even restart it from HA.
I am using GPIO pin 4 as wakeup pin for the sensor to wake the Beetle up. Also GPIO5 to drive the mosfet gate high to remove power from BMI160 (i.e. to reset it and clear the I2C lines). Both these pins are strapping pins that control the SDIO Sampling and Driving Clock edge. Could this cause these hangs? GPIO pins 4 to 7 are also JTAG pins

SDIO Sampling and Driving Clock Edge– Strapping pin: MTMS and MTDI

I notice this esp32-c6-devkitc-1 . I have used both the beetle C6 and firebeetle 2 C6. One uses esp32-c6-devkitc-1 and the other esp32-c6-devkitm-1. I can’t remember which is which. You might double check. I was able to upload code with the wrong board but crashed after several hours. EDIT! I checked my old configs and can confirm I used esp32-c6-devkitm-1

I did find the root cause was actually the wifi radio and the api connection. By reducing the power from 20 to 13.5 dBm, and setting the WiFi and API like this;

api:
  id: api_server
  reboot_timeout: 0s
  listen_backlog: 4
  max_connections: 3
  max_send_queue: 16
....

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  power_save_mode: NONE
  enable_btm: True
  enable_rrm: True
  fast_connect: true  # Skips full channel scan
  reboot_timeout: 5min       # full restart only if Wi‑Fi gone 5 min
  #post_connect_roaming: false # <--- Stops the "Roam scan" and its CPU hit
  output_power: 13.5dBm # Slightly lowering power can prevent EVM issues

solved my hang issue. The wifi issue was strange, as at full power it was operating in 802.11b mode, and I think caused bottlenecks. When I decreased the power it started operating at 11ax mode. So looks like the antenna on these beetles is not very well matched or designed

So you are saying that the DFRobot Beetle esp32-c6 should use the devkitm-1 instead of the devkitc-1?