1#![cfg_attr(target_os = "none", no_std)]
53#![cfg_attr(target_os = "none", no_main)]
54
55#[cfg(target_os = "none")]
56extern crate alloc;
57
58#[cfg(not(target_os = "none"))]
59fn main() {}
60
61#[cfg(target_os = "none")]
62mod panic;
63
64#[cfg(target_os = "none")]
65mod cli_io;
66
67#[cfg(target_os = "none")]
71mod defmt_logger {
72 #[defmt::global_logger]
73 struct Logger;
74 unsafe impl defmt::Logger for Logger {
75 fn acquire() {}
76 unsafe fn flush() {}
77 unsafe fn release() {}
78 unsafe fn write(_: &[u8]) {}
79 }
80 defmt::timestamp!("{=u32}", 0u32);
81}
82
83#[cfg(target_os = "none")]
87#[global_allocator]
88static ALLOCATOR: embedded_alloc::Heap = embedded_alloc::Heap::empty();
89
90#[cfg(target_os = "none")]
91mod firmware {
92 use core::mem::MaybeUninit;
93
94 use embassy_executor::Spawner;
95 use embassy_futures::join::join;
96 use embassy_futures::select::{Either, Either4, select, select4};
97 use embassy_nrf::Peri;
98 use embassy_nrf::bind_interrupts;
99 use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
100 use embassy_nrf::nvmc::Nvmc;
101 use embassy_nrf::peripherals;
102 use embassy_nrf::pwm::{DutyCycle, Prescaler, SimpleConfig, SimplePwm};
103 use embassy_nrf::spim::{Config as SpimConfig, Frequency, Spim};
104 use embassy_nrf::usb::Driver;
105 use embassy_nrf::usb::vbus_detect::HardwareVbusDetect;
106 use embassy_nrf::wdt::{Config as WdtConfig, Watchdog, WatchdogHandle};
107 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
108 use embassy_sync::signal::Signal;
109 use embassy_time::{Delay, Duration, Instant, Timer};
110 use embassy_usb::class::cdc_acm::{CdcAcmClass, Sender, State};
111 use embassy_usb::{Builder, Config};
112 use embedded_hal_bus::spi::ExclusiveDevice;
113 use lora_phy::LoRa;
114 use lora_phy::iv::GenericLr1110InterfaceVariant;
115 use lora_phy::lr1110::{
116 Config as LoraConfig, Lr1110, TcxoCtrlVoltage, radio_kind_params::PaSelection,
117 variant::Lr1110 as Lr1110Chip,
118 };
119 use lora_phy::mod_params::{Bandwidth, ModulationParams, PacketParams, SpreadingFactor};
120 use static_cell::StaticCell;
121 use umsh_bsp_nrf52840::cdc_rescue::CdcAcmRescue;
122 use umsh_bsp_nrf52840::flash_store;
123 use umsh_bsp_nrf52840::flash_store::{
124 NvmcChannelStore, NvmcCounterStore, NvmcPeerStore, NvmcStorage,
125 };
126 use umsh_bsp_nrf52840::panic_persist::PanicSlot;
127 use umsh_bsp_nrf52840::{EmbassyClock, Nrf52840Rng};
128 use umsh_bsp_t1000e::{PowerSignaler, RF_SWITCH, SHUTDOWN_SIGNAL, T1000EMac, T1000EPlatform};
129 use umsh_core::PublicKey;
130 use umsh_crypto::{
131 CryptoEngine, NodeIdentity,
132 software::{SoftwareAes, SoftwareIdentity, SoftwareSha256},
133 };
134 use umsh_mac::{MacHandle, OperatingPolicy, RepeaterConfig, SendOptions};
135 use umsh_node::{Host, LocalNode};
136 use umsh_sync::AsyncRefCell;
137 use umsh_ux_tracker::button::{ButtonEdge, ButtonEvent, ButtonFsm, ButtonTimings};
138 use umsh_ux_tracker::buzzer::melodies as buzzer_melodies;
139 use umsh_ux_tracker::led::{LedSequence, T1000eLedEngine};
140
141 use super::cli_io;
142
143 bind_interrupts!(struct Irqs {
144 USBD => embassy_nrf::usb::InterruptHandler<peripherals::USBD>;
145 CLOCK_POWER => embassy_nrf::usb::vbus_detect::InterruptHandler;
146 TWISPI0 => embassy_nrf::spim::InterruptHandler<peripherals::TWISPI0>;
149 SAADC => embassy_nrf::saadc::InterruptHandler;
150 });
151
152 const TX_POWER_DBM: i32 = 22;
156
157 const DEBOUNCE: Duration = Duration::from_millis(10);
158
159 type T1000EHost = Host<MacHandle<'static, T1000EPlatform, 2, 8, 4, 4, 8, 255, 32>>;
171 type T1000ENode = LocalNode<MacHandle<'static, T1000EPlatform, 2, 8, 4, 4, 8, 255, 32>>;
173
174 type RadioSpiBus = ExclusiveDevice<Spim<'static>, Output<'static>, Delay>;
177 type RadioIv = GenericLr1110InterfaceVariant<Output<'static>, Input<'static>>;
178 type RadioKindT = Lr1110<RadioSpiBus, RadioIv, Lr1110Chip>;
179 type LoraRadio = LoRa<RadioKindT, Delay>;
180
181 type RadioCh = umsh_radio_loraphy::Channels<ThreadModeRawMutex, 4, 2>;
186 static RADIO_CH: RadioCh = RadioCh::new();
187
188 static MAC_CELL: StaticCell<AsyncRefCell<T1000EMac>> = StaticCell::new();
189 static STORAGE: StaticCell<NvmcStorage> = StaticCell::new();
190
191 static BEACON_SIGNAL: Signal<ThreadModeRawMutex, ()> = Signal::new();
201
202 type T1000eUsbDriver = Driver<'static, HardwareVbusDetect>;
205 type T1000eSender = Sender<'static, T1000eUsbDriver>;
206 type T1000eRescue = CdcAcmRescue<'static, T1000eUsbDriver>;
207
208 #[embassy_executor::task]
217 async fn output_task(mut tx: T1000eSender) {
218 cli_io::drain_to_sender(&mut tx).await;
219 }
220
221 #[embassy_executor::task]
224 async fn radio_runner_task(
225 lora: LoraRadio,
226 mdltn: ModulationParams,
227 rx_pkt: PacketParams,
228 tx_pkt: PacketParams,
229 ) {
230 umsh_radio_loraphy::runner(lora, &RADIO_CH, mdltn, rx_pkt, tx_pkt, TX_POWER_DBM, None)
231 .await;
232 }
233
234 #[embassy_executor::task]
238 async fn buzzer_task(
239 pwm: SimplePwm<'static>,
240 enable: Output<'static>,
241 initially_silenced: bool,
242 ) {
243 umsh_bsp_t1000e::buzzer::run(pwm, enable, initially_silenced).await;
244 }
245
246 #[embassy_executor::task]
249 async fn mac_task(mut host: T1000EHost) {
250 let _ = host.run().await;
251 panic!("host exited");
252 }
253
254 #[embassy_executor::task]
258 async fn beacon_task(beacon_node: T1000ENode) {
259 use umsh_node::Transport as _;
260 loop {
261 BEACON_SIGNAL.wait().await;
262 umsh_bsp_t1000e::BUZZER_SIGNAL.signal(&buzzer_melodies::BEACON_ACK);
265 let options = SendOptions::default().with_trace_route();
266 let _ = beacon_node.send_all(&[], &options).await;
267 }
268 }
269
270 #[embassy_executor::task]
274 async fn cli_task(
275 node: T1000ENode,
276 local_key: PublicKey,
277 storage: &'static NvmcStorage,
278 rx: T1000eRescue,
279 prev_panic_buf: &'static [u8; 256],
280 prev_panic_len: usize,
281 ) {
282 use umsh_cli::CliSession;
283 use umsh_cli::io::CliOutput;
284 use umsh_cli::logger::NullLogger;
285
286 let mut input = cli_io::CdcInput::new(rx);
287 let mut out = cli_io::CdcOutput::new();
288
289 input.wait_connection().await;
291
292 let _ = out.write_line("").await;
293 let _ = out.write_line("UMSH CLI (T1000-E)").await;
294 let _ = out.write_line("type /help for commands").await;
295 if prev_panic_len > 0 {
296 let _ = out.write_line("[PREV PANIC]:").await;
297 if let Ok(s) = core::str::from_utf8(&prev_panic_buf[..prev_panic_len]) {
298 let _ = out.write_line(s).await;
299 }
300 }
301
302 let peer_store = NvmcPeerStore::new(storage);
303 let channel_store = NvmcChannelStore::new(storage);
304 let mut cli: CliSession<_, _, _, _, _, _, 4, 4, 2, 8, 128> = CliSession::new(
305 node,
306 local_key,
307 out,
308 NullLogger::new(),
309 peer_store,
310 channel_store,
311 PowerSignaler,
312 );
313
314 let _ = cli.run(&mut input).await;
317 panic!("cli exited");
318 }
319
320 #[embassy_executor::task]
324 async fn button_task(mut button: Input<'static>, storage: &'static NvmcStorage) {
325 let mut fsm = ButtonFsm::new(ButtonTimings::default());
326 let mut pressed = button.is_high();
327 loop {
328 let event = {
329 let now_ms = Instant::now().as_millis();
330 let edge_fut = async {
331 if pressed {
332 button.wait_for_low().await;
333 Timer::after(DEBOUNCE).await;
334 ButtonEdge::Release
335 } else {
336 button.wait_for_high().await;
337 Timer::after(DEBOUNCE).await;
338 ButtonEdge::Press
339 }
340 };
341 let timeout_deadline_ms =
342 fsm.next_deadline().unwrap_or(now_ms.saturating_add(60_000));
343 let timer_fut = Timer::at(Instant::from_millis(timeout_deadline_ms));
344 match select(edge_fut, timer_fut).await {
345 Either::First(edge) => {
346 pressed = matches!(edge, ButtonEdge::Press);
347 fsm.on_edge(edge, Instant::now().as_millis())
348 }
349 Either::Second(()) => fsm.poll(Instant::now().as_millis()),
350 }
351 };
352
353 match event {
354 Some(ButtonEvent::Single) => {
355 umsh_bsp_t1000e::indicator::LED_SEQUENCE_SIGNAL
356 .signal(LedSequence::ActionConfirm);
357 BEACON_SIGNAL.signal(());
358 }
359 Some(ButtonEvent::Double) => {
360 let preferences = umsh_bsp_t1000e::preferences::toggle_silent();
361 umsh_bsp_t1000e::BUZZER_SILENCE_SET.signal(preferences.silent);
362 umsh_bsp_t1000e::indicator::LED_SEQUENCE_SIGNAL
363 .signal(LedSequence::ActionConfirm);
364 let mut durable = preferences;
368 durable.battery_critical = false;
369 let _ = storage.store_tracker_preferences(durable.encode()).await;
370 }
371 Some(ButtonEvent::Triple) => {
372 }
375 Some(ButtonEvent::Quad) => {
376 }
378 Some(ButtonEvent::Long) => {
379 pressed = false;
380 fsm = ButtonFsm::new(ButtonTimings::default());
381 umsh_bsp_t1000e::preferences::set_asleep(true);
384 SHUTDOWN_SIGNAL.signal(());
385 }
386 _ => {}
387 }
388 }
389 }
390
391 #[embassy_executor::task]
395 async fn shutdown_task(storage: &'static NvmcStorage) -> ! {
396 SHUTDOWN_SIGNAL.wait().await;
397 let mut durable = umsh_bsp_t1000e::preferences::load();
398 durable.battery_critical = false;
399 let _ = storage.store_tracker_preferences(durable.encode()).await;
400 umsh_bsp_t1000e::shutdown::run_after_request().await
401 }
402
403 #[embassy_executor::task]
406 async fn power_task(
407 saadc: Peri<'static, peripherals::SAADC>,
408 battery_pin: Peri<'static, peripherals::P0_02>,
409 light_pin: Peri<'static, peripherals::P0_29>,
410 sensor_rail: Output<'static>,
411 sensor_enable: Output<'static>,
412 external_power: Input<'static>,
413 charge_active: Input<'static>,
414 ) {
415 umsh_bsp_t1000e::power::run_battery_monitor(
416 saadc,
417 Irqs,
418 battery_pin,
419 light_pin,
420 sensor_rail,
421 sensor_enable,
422 external_power,
423 charge_active,
424 )
425 .await;
426 }
427
428 async fn heartbeat(mut led: SimplePwm<'static>, mut wdt: WatchdogHandle) -> ! {
429 led.set_period(1_000);
430 led.enable();
431 let mut engine = T1000eLedEngine::new(Instant::now().as_millis());
432 loop {
433 wdt.pet();
434 engine.set_battery(umsh_bsp_t1000e::battery_state());
435 engine.set_attention(umsh_bsp_t1000e::indicator::attention_requested());
436 let decision = engine.tick(Instant::now().as_millis());
437 let duty =
438 ((u32::from(led.max_duty()) * u32::from(decision.brightness)) / 1_000) as u16;
439 let blanking = umsh_bsp_t1000e::indicator::blank_requested();
443 led.set_duty(0, DutyCycle::inverted(if blanking { 0 } else { duty }));
444 if blanking {
445 umsh_bsp_t1000e::indicator::confirm_blanked();
446 }
447 match select(
448 select4(
449 Timer::at(Instant::from_millis(decision.next_deadline_ms)),
450 umsh_bsp_t1000e::BATTERY_STATE_CHANGED.wait(),
451 umsh_bsp_t1000e::indicator::INDICATOR_CHANGED.wait(),
452 umsh_bsp_t1000e::indicator::LED_SEQUENCE_SIGNAL.wait(),
453 ),
454 umsh_bsp_t1000e::indicator::LED_BLANK_CHANGED.wait(),
455 )
456 .await
457 {
458 Either::First(Either4::First(()))
459 | Either::First(Either4::Second(_))
460 | Either::First(Either4::Third(()))
461 | Either::Second(()) => {}
462 Either::First(Either4::Fourth(sequence)) => {
463 engine.play(sequence, Instant::now().as_millis());
464 }
465 }
466 }
467 }
468
469 #[embassy_executor::main]
470 async fn main(spawner: Spawner) {
471 {
474 const HEAP_SIZE: usize = 8192;
475 static mut HEAP: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
476 unsafe { crate::ALLOCATOR.init(core::ptr::addr_of!(HEAP) as usize, HEAP_SIZE) }
477 }
478
479 let p = embassy_nrf::init(umsh_bsp_nrf52840::clocks::default_config());
480
481 let storage: &'static NvmcStorage =
485 STORAGE.init(flash_store::new_storage(Nvmc::new(p.NVMC)));
486
487 let reset_reasons = embassy_nrf::pac::POWER.resetreas().read();
488 embassy_nrf::pac::POWER
489 .resetreas()
490 .write(|reasons| reasons.0 = u32::MAX);
491 let woke_from_system_off = reset_reasons.off();
498 let external_power_present = umsh_bsp_t1000e::power::usb_power_present();
499 let gpregret_state = umsh_bsp_t1000e::preferences::load_retained();
500 let retained_critical =
501 gpregret_state.is_some_and(|preferences| preferences.battery_critical);
502 let mut retained_state = storage
503 .load_tracker_preferences()
504 .await
505 .ok()
506 .flatten()
507 .and_then(umsh_ux_tracker::state::UserPreferences::try_decode)
508 .unwrap_or_default();
509 retained_state.battery_critical = retained_critical;
510 umsh_bsp_t1000e::preferences::store(retained_state);
511 if retained_state.battery_critical && !external_power_present {
512 umsh_bsp_t1000e::shutdown::resume_persisted_sleep().await;
513 }
514 if retained_state.battery_critical && external_power_present {
515 umsh_bsp_t1000e::preferences::set_battery_critical(false);
516 }
517 let charging_sleep_wake_requested =
518 reset_reasons.sreq() && gpregret_state.is_some_and(|preferences| !preferences.asleep);
519 if woke_from_system_off || charging_sleep_wake_requested {
520 let preferences = umsh_bsp_t1000e::preferences::set_asleep(false);
521 let mut durable = preferences;
522 durable.battery_critical = false;
523 let _ = storage.store_tracker_preferences(durable.encode()).await;
524 } else if umsh_bsp_t1000e::preferences::load().asleep {
525 if external_power_present {
526 let mut led_config = SimpleConfig::default();
527 led_config.prescaler = Prescaler::Div16;
528 let led_pwm = SimplePwm::new_1ch(p.PWM1, p.P0_24, &led_config);
529 let sleep_button = Input::new(p.P0_06, Pull::Down);
530 let sleep_external_power = Input::new(p.P0_05, Pull::Down);
531 let sleep_charge_active = Input::new(p.P1_03, Pull::Up);
532 umsh_bsp_t1000e::shutdown::run_charging_sleep(
533 led_pwm,
534 sleep_button,
535 sleep_external_power,
536 sleep_charge_active,
537 )
538 .await;
539 } else {
540 umsh_bsp_t1000e::shutdown::resume_persisted_sleep().await;
541 }
542 }
543
544 let radio_rst = Output::new(p.P1_10, Level::Low, OutputDrive::Standard);
549
550 let button = Input::new(p.P0_06, Pull::Down);
553
554 let mut wdt_config = WdtConfig::default();
556 wdt_config.timeout_ticks = 32768 * 8;
557 let (_wdt, [wdt_handle]) =
558 Watchdog::try_new::<_, 1>(p.WDT, wdt_config).unwrap_or_else(|_| panic!("wdt"));
559
560 static PREV_PANIC_BUF: StaticCell<[u8; 256]> = StaticCell::new();
562 let mut prev_panic_tmp = [0u8; 256];
563 let prev_panic_len = {
564 let mut slot = PanicSlot::new(super::panic::panic_region());
565 if let Some(msg) = slot.read() {
566 let n = msg.len().min(prev_panic_tmp.len());
567 prev_panic_tmp[..n].copy_from_slice(&msg[..n]);
568 slot.clear();
569 n
570 } else {
571 0
572 }
573 };
574 let prev_panic_buf: &'static [u8; 256] = PREV_PANIC_BUF.init(prev_panic_tmp);
575
576 let mut led_config = SimpleConfig::default();
577 led_config.prescaler = Prescaler::Div16;
578 let led = SimplePwm::new_1ch(p.PWM1, p.P0_24, &led_config);
579
580 let buzzer_pwm = {
585 let mut cfg = SimpleConfig::default();
586 cfg.prescaler = Prescaler::Div16;
587 SimplePwm::new_1ch(p.PWM0, p.P0_25, &cfg)
588 };
589 let buzzer_enable = Output::new(p.P1_05, Level::Low, OutputDrive::Standard);
590 let initial_preferences = umsh_bsp_t1000e::preferences::load();
591 spawner.spawn(buzzer_task(buzzer_pwm, buzzer_enable, initial_preferences.silent).unwrap());
592 umsh_bsp_t1000e::BUZZER_SIGNAL.signal(&buzzer_melodies::POWER_ON);
594
595 let mut rng = Nrf52840Rng::new(p.RNG);
604 let sk_bytes: [u8; 32] = match storage.load_sk().await {
605 Ok(Some(sk)) => sk,
606 Ok(None) => {
607 let mut sk = [0u8; 32];
608 rng.fill_bytes(&mut sk);
609 storage
610 .store_sk(&sk)
611 .await
612 .unwrap_or_else(|_| panic!("identity persist"));
613 sk
614 }
615 Err(_) => panic!("storage init failed"),
616 };
617 let identity = SoftwareIdentity::from_secret_bytes(&sk_bytes);
618 let local_key = *identity.public_key();
619
620 let t_frame_ms = umsh_radio_loraphy::airtime_ms(
627 SpreadingFactor::_7,
628 Bandwidth::_62KHz,
629 umsh_radio_loraphy::MAX_PAYLOAD,
630 );
631 {
632 let mut cfg = SpimConfig::default();
633 cfg.frequency = Frequency::M8;
634 let radio_bus = Spim::new(
635 p.TWISPI0, Irqs, p.P0_11, p.P1_08, p.P1_09, cfg,
639 );
640 let radio_cs = Output::new(p.P0_12, Level::High, OutputDrive::Standard);
641 let radio_spi = ExclusiveDevice::new(radio_bus, radio_cs, Delay).unwrap();
642
643 let radio_interrupt = Input::new(p.P1_01, Pull::Down);
646 let radio_busy = Input::new(p.P0_07, Pull::None);
647
648 let iv = GenericLr1110InterfaceVariant::new(
649 radio_rst,
650 radio_interrupt,
651 radio_busy,
652 None, None, )
655 .unwrap_or_else(|_| panic!("lr1110 iv"));
656
657 let lora_config = LoraConfig {
658 chip: Lr1110Chip::with_pa(PaSelection::Hp),
662 tcxo_ctrl: Some(TcxoCtrlVoltage::Ctrl1V6),
663 use_dcdc: false, rx_boost: true,
665 rf_switch: Some(RF_SWITCH),
666 };
667
668 let mut lora = LoRa::new(Lr1110::new(radio_spi, iv, lora_config), false, Delay)
671 .await
672 .unwrap_or_else(|_| panic!("radio init"));
673
674 let (mdltn, rx_pkt, tx_pkt) = umsh_radio_loraphy::profile_params(
678 &mut lora,
679 umsh_radio_loraphy::profiles::DEFAULT,
680 16,
681 )
682 .unwrap_or_else(|_| panic!("radio params"));
683
684 spawner.spawn(radio_runner_task(lora, mdltn, rx_pkt, tx_pkt).unwrap());
685 }
686
687 let radio_handle = umsh_radio_loraphy::LoraphyRadio::new(&RADIO_CH, t_frame_ms);
689 let crypto = CryptoEngine::new(SoftwareAes, SoftwareSha256);
690 let mut mac = T1000EMac::new(
691 radio_handle,
692 crypto,
693 EmbassyClock,
694 rng,
695 NvmcCounterStore::new(storage),
696 RepeaterConfig::default(),
697 OperatingPolicy::default(),
698 );
699 let identity_id = mac
700 .add_identity(identity)
701 .unwrap_or_else(|_| panic!("identity"));
702 mac.load_persisted_counter(identity_id)
704 .await
705 .unwrap_or_else(|_| panic!("tx counter load"));
706 let mac_cell: &'static AsyncRefCell<T1000EMac> = MAC_CELL.init(AsyncRefCell::new(mac));
707
708 let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs));
710
711 let mut config = Config::new(0x2886, 0x0057);
712 config.manufacturer = Some("Seeed");
713 config.product = Some("T1000-E UMSH CLI");
714 config.serial_number = Some("umsh-t1000e");
715 config.max_power = 100;
716 config.max_packet_size_0 = 64;
717
718 static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new();
719 static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new();
720 static MSOS_DESC: StaticCell<[u8; 0]> = StaticCell::new();
721 static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
722 static STATE: StaticCell<State> = StaticCell::new();
723
724 let mut builder = Builder::new(
725 driver,
726 config,
727 CONFIG_DESC.init([0; 256]),
728 BOS_DESC.init([0; 256]),
729 MSOS_DESC.init([0; 0]),
730 CONTROL_BUF.init([0; 64]),
731 );
732
733 let class = CdcAcmClass::new(&mut builder, STATE.init(State::new()), 64);
734 let mut usb = builder.build();
735
736 let (tx, raw_rx, ctrl) = class.split_with_control();
737 let rx = CdcAcmRescue::new(raw_rx, ctrl);
738
739 let sensor_rail = Output::new(p.P1_06, Level::Low, OutputDrive::Standard);
744 let sensor_enable = Output::new(p.P0_04, Level::Low, OutputDrive::Standard);
745 let external_power = Input::new(p.P0_05, Pull::Down);
746 let charge_active = Input::new(p.P1_03, Pull::Up);
747
748 let handle = MacHandle::new(mac_cell);
755 let mut host: T1000EHost = Host::new(handle);
756 let node = host.add_node(identity_id);
757 let beacon_node = node.clone();
758
759 {
764 let mut peer_buf: heapless::Vec<([u8; 32], Option<heapless::String<16>>), 8> =
765 heapless::Vec::new();
766 let _ = storage.load_all_peers(&mut peer_buf).await;
767 let mut ch_buf: heapless::Vec<(heapless::String<16>, [u8; 32]), 2> =
768 heapless::Vec::new();
769 let _ = storage.load_all_channels(&mut ch_buf).await;
770 for (pk, _alias) in peer_buf.iter() {
771 let _ = node.peer(PublicKey(*pk)).await;
772 }
773 for (name, key_bytes) in ch_buf.iter() {
774 let channel =
775 umsh_node::Channel::private(umsh_core::ChannelKey(*key_bytes), name.as_str());
776 let _ = node.join(&channel).await;
777 }
778 }
779
780 MacHandle::new(mac_cell)
785 .load_all_persisted_rx_counters()
786 .await
787 .ok();
788
789 spawner.spawn(output_task(tx).unwrap());
790 spawner.spawn(button_task(button, storage).unwrap());
791 spawner.spawn(shutdown_task(storage).unwrap());
792 spawner.spawn(
793 power_task(
794 p.SAADC,
795 p.P0_02,
796 p.P0_29,
797 sensor_rail,
798 sensor_enable,
799 external_power,
800 charge_active,
801 )
802 .unwrap(),
803 );
804 spawner.spawn(mac_task(host).unwrap());
805 spawner.spawn(beacon_task(beacon_node).unwrap());
806 spawner
807 .spawn(cli_task(node, local_key, storage, rx, prev_panic_buf, prev_panic_len).unwrap());
808
809 join(usb.run(), heartbeat(led, wdt_handle)).await;
810 }
811}