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::{
120 Bandwidth, CodingRate, ModulationParams, PacketParams, SpreadingFactor,
121 };
122 use static_cell::StaticCell;
123 use umsh_bsp_nrf52840::cdc_rescue::CdcAcmRescue;
124 use umsh_bsp_nrf52840::flash_store;
125 use umsh_bsp_nrf52840::flash_store::{
126 NvmcChannelStore, NvmcCounterStore, NvmcPeerStore, NvmcStorage,
127 };
128 use umsh_bsp_nrf52840::panic_persist::PanicSlot;
129 use umsh_bsp_nrf52840::{EmbassyClock, Nrf52840Rng};
130 use umsh_bsp_t1000e::{PowerSignaler, RF_SWITCH, SHUTDOWN_SIGNAL, T1000EMac, T1000EPlatform};
131 use umsh_core::PublicKey;
132 use umsh_crypto::{
133 CryptoEngine, NodeIdentity,
134 software::{SoftwareAes, SoftwareIdentity, SoftwareSha256},
135 };
136 use umsh_mac::{MacHandle, OperatingPolicy, RepeaterConfig, SendOptions};
137 use umsh_node::{Host, LocalNode};
138 use umsh_sync::AsyncRefCell;
139 use umsh_ux_tracker::button::{ButtonEdge, ButtonEvent, ButtonFsm, ButtonTimings};
140 use umsh_ux_tracker::buzzer::melodies as buzzer_melodies;
141 use umsh_ux_tracker::led::{LedSequence, T1000eLedEngine};
142
143 use super::cli_io;
144
145 bind_interrupts!(struct Irqs {
146 USBD => embassy_nrf::usb::InterruptHandler<peripherals::USBD>;
147 CLOCK_POWER => embassy_nrf::usb::vbus_detect::InterruptHandler;
148 TWISPI0 => embassy_nrf::spim::InterruptHandler<peripherals::TWISPI0>;
151 SAADC => embassy_nrf::saadc::InterruptHandler;
152 });
153
154 const TX_POWER_DBM: i32 = 22;
158
159 const DEBOUNCE: Duration = Duration::from_millis(10);
160
161 type T1000EHost = Host<MacHandle<'static, T1000EPlatform, 2, 8, 4, 4, 8, 255, 32>>;
173 type T1000ENode = LocalNode<MacHandle<'static, T1000EPlatform, 2, 8, 4, 4, 8, 255, 32>>;
175
176 type RadioSpiBus = ExclusiveDevice<Spim<'static>, Output<'static>, Delay>;
179 type RadioIv = GenericLr1110InterfaceVariant<Output<'static>, Input<'static>>;
180 type RadioKindT = Lr1110<RadioSpiBus, RadioIv, Lr1110Chip>;
181 type LoraRadio = LoRa<RadioKindT, Delay>;
182
183 type RadioCh = umsh_radio_loraphy::Channels<ThreadModeRawMutex, 4, 2>;
188 static RADIO_CH: RadioCh = RadioCh::new();
189
190 static MAC_CELL: StaticCell<AsyncRefCell<T1000EMac>> = StaticCell::new();
191 static STORAGE: StaticCell<NvmcStorage> = StaticCell::new();
192
193 static BEACON_SIGNAL: Signal<ThreadModeRawMutex, ()> = Signal::new();
203
204 type T1000eUsbDriver = Driver<'static, HardwareVbusDetect>;
207 type T1000eSender = Sender<'static, T1000eUsbDriver>;
208 type T1000eRescue = CdcAcmRescue<'static, T1000eUsbDriver>;
209
210 #[embassy_executor::task]
219 async fn output_task(mut tx: T1000eSender) {
220 cli_io::drain_to_sender(&mut tx).await;
221 }
222
223 #[embassy_executor::task]
226 async fn radio_runner_task(
227 lora: LoraRadio,
228 mdltn: ModulationParams,
229 rx_pkt: PacketParams,
230 tx_pkt: PacketParams,
231 ) {
232 umsh_radio_loraphy::runner(lora, &RADIO_CH, mdltn, rx_pkt, tx_pkt, TX_POWER_DBM).await;
233 }
234
235 #[embassy_executor::task]
239 async fn buzzer_task(
240 pwm: SimplePwm<'static>,
241 enable: Output<'static>,
242 initially_silenced: bool,
243 ) {
244 umsh_bsp_t1000e::buzzer::run(pwm, enable, initially_silenced).await;
245 }
246
247 #[embassy_executor::task]
250 async fn mac_task(mut host: T1000EHost) {
251 let _ = host.run().await;
252 panic!("host exited");
253 }
254
255 #[embassy_executor::task]
259 async fn beacon_task(beacon_node: T1000ENode) {
260 use umsh_node::Transport as _;
261 loop {
262 BEACON_SIGNAL.wait().await;
263 umsh_bsp_t1000e::BUZZER_SIGNAL.signal(&buzzer_melodies::BEACON_ACK);
266 let options = SendOptions::default().with_trace_route();
267 let _ = beacon_node.send_all(&[], &options).await;
268 }
269 }
270
271 #[embassy_executor::task]
275 async fn cli_task(
276 node: T1000ENode,
277 local_key: PublicKey,
278 storage: &'static NvmcStorage,
279 rx: T1000eRescue,
280 prev_panic_buf: &'static [u8; 256],
281 prev_panic_len: usize,
282 ) {
283 use umsh_cli::CliSession;
284 use umsh_cli::io::CliOutput;
285 use umsh_cli::logger::NullLogger;
286
287 let mut input = cli_io::CdcInput::new(rx);
288 let mut out = cli_io::CdcOutput::new();
289
290 input.wait_connection().await;
292
293 let _ = out.write_line("").await;
294 let _ = out.write_line("UMSH CLI (T1000-E)").await;
295 let _ = out.write_line("type /help for commands").await;
296 if prev_panic_len > 0 {
297 let _ = out.write_line("[PREV PANIC]:").await;
298 if let Ok(s) = core::str::from_utf8(&prev_panic_buf[..prev_panic_len]) {
299 let _ = out.write_line(s).await;
300 }
301 }
302
303 let peer_store = NvmcPeerStore::new(storage);
304 let channel_store = NvmcChannelStore::new(storage);
305 let mut cli: CliSession<_, _, _, _, _, _, 4, 4, 2, 8, 128> = CliSession::new(
306 node,
307 local_key,
308 out,
309 NullLogger::new(),
310 peer_store,
311 channel_store,
312 PowerSignaler,
313 );
314
315 let _ = cli.run(&mut input).await;
318 panic!("cli exited");
319 }
320
321 #[embassy_executor::task]
325 async fn button_task(mut button: Input<'static>, storage: &'static NvmcStorage) {
326 let mut fsm = ButtonFsm::new(ButtonTimings::default());
327 let mut pressed = button.is_high();
328 loop {
329 let event = {
330 let now_ms = Instant::now().as_millis();
331 let edge_fut = async {
332 if pressed {
333 button.wait_for_low().await;
334 Timer::after(DEBOUNCE).await;
335 ButtonEdge::Release
336 } else {
337 button.wait_for_high().await;
338 Timer::after(DEBOUNCE).await;
339 ButtonEdge::Press
340 }
341 };
342 let timeout_deadline_ms =
343 fsm.next_deadline().unwrap_or(now_ms.saturating_add(60_000));
344 let timer_fut = Timer::at(Instant::from_millis(timeout_deadline_ms));
345 match select(edge_fut, timer_fut).await {
346 Either::First(edge) => {
347 pressed = matches!(edge, ButtonEdge::Press);
348 fsm.on_edge(edge, Instant::now().as_millis())
349 }
350 Either::Second(()) => fsm.poll(Instant::now().as_millis()),
351 }
352 };
353
354 match event {
355 Some(ButtonEvent::Single) => {
356 umsh_bsp_t1000e::indicator::LED_SEQUENCE_SIGNAL
357 .signal(LedSequence::ActionConfirm);
358 BEACON_SIGNAL.signal(());
359 }
360 Some(ButtonEvent::Double) => {
361 let preferences = umsh_bsp_t1000e::preferences::toggle_silent();
362 umsh_bsp_t1000e::BUZZER_SILENCE_SET.signal(preferences.silent);
363 umsh_bsp_t1000e::indicator::LED_SEQUENCE_SIGNAL
364 .signal(LedSequence::ActionConfirm);
365 let mut durable = preferences;
369 durable.battery_critical = false;
370 let _ = storage.store_tracker_preferences(durable.encode()).await;
371 }
372 Some(ButtonEvent::Triple) => {
373 }
376 Some(ButtonEvent::Quad) => {
377 }
379 Some(ButtonEvent::Long) => {
380 pressed = false;
381 fsm = ButtonFsm::new(ButtonTimings::default());
382 umsh_bsp_t1000e::preferences::set_asleep(true);
385 SHUTDOWN_SIGNAL.signal(());
386 }
387 _ => {}
388 }
389 }
390 }
391
392 #[embassy_executor::task]
396 async fn shutdown_task(storage: &'static NvmcStorage) -> ! {
397 SHUTDOWN_SIGNAL.wait().await;
398 let mut durable = umsh_bsp_t1000e::preferences::load();
399 durable.battery_critical = false;
400 let _ = storage.store_tracker_preferences(durable.encode()).await;
401 umsh_bsp_t1000e::shutdown::run_after_request().await
402 }
403
404 #[embassy_executor::task]
407 async fn power_task(
408 saadc: Peri<'static, peripherals::SAADC>,
409 battery_pin: Peri<'static, peripherals::P0_02>,
410 light_pin: Peri<'static, peripherals::P0_29>,
411 sensor_rail: Output<'static>,
412 sensor_enable: Output<'static>,
413 external_power: Input<'static>,
414 charge_active: Input<'static>,
415 ) {
416 umsh_bsp_t1000e::power::run_battery_monitor(
417 saadc,
418 Irqs,
419 battery_pin,
420 light_pin,
421 sensor_rail,
422 sensor_enable,
423 external_power,
424 charge_active,
425 )
426 .await;
427 }
428
429 async fn heartbeat(mut led: SimplePwm<'static>, mut wdt: WatchdogHandle) -> ! {
430 led.set_period(1_000);
431 led.enable();
432 let mut engine = T1000eLedEngine::new(Instant::now().as_millis());
433 loop {
434 wdt.pet();
435 engine.set_battery(umsh_bsp_t1000e::battery_state());
436 engine.set_attention(umsh_bsp_t1000e::indicator::attention_requested());
437 let decision = engine.tick(Instant::now().as_millis());
438 let duty =
439 ((u32::from(led.max_duty()) * u32::from(decision.brightness)) / 1_000) as u16;
440 let blanking = umsh_bsp_t1000e::indicator::blank_requested();
444 led.set_duty(0, DutyCycle::inverted(if blanking { 0 } else { duty }));
445 if blanking {
446 umsh_bsp_t1000e::indicator::confirm_blanked();
447 }
448 match select(
449 select4(
450 Timer::at(Instant::from_millis(decision.next_deadline_ms)),
451 umsh_bsp_t1000e::BATTERY_STATE_CHANGED.wait(),
452 umsh_bsp_t1000e::indicator::INDICATOR_CHANGED.wait(),
453 umsh_bsp_t1000e::indicator::LED_SEQUENCE_SIGNAL.wait(),
454 ),
455 umsh_bsp_t1000e::indicator::LED_BLANK_CHANGED.wait(),
456 )
457 .await
458 {
459 Either::First(Either4::First(()))
460 | Either::First(Either4::Second(_))
461 | Either::First(Either4::Third(()))
462 | Either::Second(()) => {}
463 Either::First(Either4::Fourth(sequence)) => {
464 engine.play(sequence, Instant::now().as_millis());
465 }
466 }
467 }
468 }
469
470 #[embassy_executor::main]
471 async fn main(spawner: Spawner) {
472 {
475 const HEAP_SIZE: usize = 8192;
476 static mut HEAP: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
477 unsafe { crate::ALLOCATOR.init(core::ptr::addr_of!(HEAP) as usize, HEAP_SIZE) }
478 }
479
480 let p = embassy_nrf::init(umsh_bsp_nrf52840::clocks::default_config());
481
482 let storage: &'static NvmcStorage =
486 STORAGE.init(flash_store::new_storage(Nvmc::new(p.NVMC)));
487
488 let reset_reasons = embassy_nrf::pac::POWER.resetreas().read();
489 embassy_nrf::pac::POWER
490 .resetreas()
491 .write(|reasons| reasons.0 = u32::MAX);
492 let woke_from_system_off = reset_reasons.off();
499 let external_power_present = umsh_bsp_t1000e::power::usb_power_present();
500 let gpregret_state = umsh_bsp_t1000e::preferences::load_retained();
501 let retained_critical =
502 gpregret_state.is_some_and(|preferences| preferences.battery_critical);
503 let mut retained_state = storage
504 .load_tracker_preferences()
505 .await
506 .ok()
507 .flatten()
508 .and_then(umsh_ux_tracker::state::UserPreferences::try_decode)
509 .unwrap_or_default();
510 retained_state.battery_critical = retained_critical;
511 umsh_bsp_t1000e::preferences::store(retained_state);
512 if retained_state.battery_critical && !external_power_present {
513 umsh_bsp_t1000e::shutdown::resume_persisted_sleep().await;
514 }
515 if retained_state.battery_critical && external_power_present {
516 umsh_bsp_t1000e::preferences::set_battery_critical(false);
517 }
518 let charging_sleep_wake_requested =
519 reset_reasons.sreq() && gpregret_state.is_some_and(|preferences| !preferences.asleep);
520 if woke_from_system_off || charging_sleep_wake_requested {
521 let preferences = umsh_bsp_t1000e::preferences::set_asleep(false);
522 let mut durable = preferences;
523 durable.battery_critical = false;
524 let _ = storage.store_tracker_preferences(durable.encode()).await;
525 } else if umsh_bsp_t1000e::preferences::load().asleep {
526 if external_power_present {
527 let mut led_config = SimpleConfig::default();
528 led_config.prescaler = Prescaler::Div16;
529 let led_pwm = SimplePwm::new_1ch(p.PWM1, p.P0_24, &led_config);
530 let sleep_button = Input::new(p.P0_06, Pull::Down);
531 let sleep_external_power = Input::new(p.P0_05, Pull::Down);
532 let sleep_charge_active = Input::new(p.P1_03, Pull::Up);
533 umsh_bsp_t1000e::shutdown::run_charging_sleep(
534 led_pwm,
535 sleep_button,
536 sleep_external_power,
537 sleep_charge_active,
538 )
539 .await;
540 } else {
541 umsh_bsp_t1000e::shutdown::resume_persisted_sleep().await;
542 }
543 }
544
545 let radio_rst = Output::new(p.P1_10, Level::Low, OutputDrive::Standard);
550
551 let button = Input::new(p.P0_06, Pull::Down);
554
555 let mut wdt_config = WdtConfig::default();
557 wdt_config.timeout_ticks = 32768 * 8;
558 let (_wdt, [wdt_handle]) =
559 Watchdog::try_new::<_, 1>(p.WDT, wdt_config).unwrap_or_else(|_| panic!("wdt"));
560
561 static PREV_PANIC_BUF: StaticCell<[u8; 256]> = StaticCell::new();
563 let mut prev_panic_tmp = [0u8; 256];
564 let prev_panic_len = {
565 let mut slot = PanicSlot::new(super::panic::panic_region());
566 if let Some(msg) = slot.read() {
567 let n = msg.len().min(prev_panic_tmp.len());
568 prev_panic_tmp[..n].copy_from_slice(&msg[..n]);
569 slot.clear();
570 n
571 } else {
572 0
573 }
574 };
575 let prev_panic_buf: &'static [u8; 256] = PREV_PANIC_BUF.init(prev_panic_tmp);
576
577 let mut led_config = SimpleConfig::default();
578 led_config.prescaler = Prescaler::Div16;
579 let led = SimplePwm::new_1ch(p.PWM1, p.P0_24, &led_config);
580
581 let buzzer_pwm = {
586 let mut cfg = SimpleConfig::default();
587 cfg.prescaler = Prescaler::Div16;
588 SimplePwm::new_1ch(p.PWM0, p.P0_25, &cfg)
589 };
590 let buzzer_enable = Output::new(p.P1_05, Level::Low, OutputDrive::Standard);
591 let initial_preferences = umsh_bsp_t1000e::preferences::load();
592 spawner.spawn(buzzer_task(buzzer_pwm, buzzer_enable, initial_preferences.silent).unwrap());
593 umsh_bsp_t1000e::BUZZER_SIGNAL.signal(&buzzer_melodies::POWER_ON);
595
596 let mut rng = Nrf52840Rng::new(p.RNG);
605 let sk_bytes: [u8; 32] = match storage.load_sk().await {
606 Ok(Some(sk)) => sk,
607 Ok(None) => {
608 let mut sk = [0u8; 32];
609 rng.fill_bytes(&mut sk);
610 storage
611 .store_sk(&sk)
612 .await
613 .unwrap_or_else(|_| panic!("identity persist"));
614 sk
615 }
616 Err(_) => panic!("storage init failed"),
617 };
618 let identity = SoftwareIdentity::from_secret_bytes(&sk_bytes);
619 let local_key = *identity.public_key();
620
621 let t_frame_ms = umsh_radio_loraphy::airtime_ms(
628 SpreadingFactor::_7,
629 Bandwidth::_62KHz,
630 umsh_radio_loraphy::MAX_PAYLOAD,
631 );
632 {
633 let mut cfg = SpimConfig::default();
634 cfg.frequency = Frequency::M8;
635 let radio_bus = Spim::new(
636 p.TWISPI0, Irqs, p.P0_11, p.P1_08, p.P1_09, cfg,
640 );
641 let radio_cs = Output::new(p.P0_12, Level::High, OutputDrive::Standard);
642 let radio_spi = ExclusiveDevice::new(radio_bus, radio_cs, Delay).unwrap();
643
644 let radio_interrupt = Input::new(p.P1_01, Pull::Down);
647 let radio_busy = Input::new(p.P0_07, Pull::None);
648
649 let iv = GenericLr1110InterfaceVariant::new(
650 radio_rst,
651 radio_interrupt,
652 radio_busy,
653 None, None, )
656 .unwrap_or_else(|_| panic!("lr1110 iv"));
657
658 let lora_config = LoraConfig {
659 chip: Lr1110Chip::with_pa(PaSelection::Hp),
663 tcxo_ctrl: Some(TcxoCtrlVoltage::Ctrl1V6),
664 use_dcdc: false, rx_boost: true,
666 rf_switch: Some(RF_SWITCH),
667 };
668
669 let mut lora = LoRa::new(Lr1110::new(radio_spi, iv, lora_config), false, Delay)
672 .await
673 .unwrap_or_else(|_| panic!("radio init"));
674
675 let mdltn = lora
685 .create_modulation_params(
686 SpreadingFactor::_7,
687 Bandwidth::_62KHz,
688 CodingRate::_4_5,
689 910_525_000,
690 )
691 .unwrap_or_else(|_| panic!("modulation params"));
692 let rx_pkt = lora
693 .create_rx_packet_params(
694 16, false, 255, true, false, &mdltn,
700 )
701 .unwrap_or_else(|_| panic!("rx packet params"));
702 let tx_pkt = lora
703 .create_tx_packet_params(
704 16, false, true, false, &mdltn,
709 )
710 .unwrap_or_else(|_| panic!("tx packet params"));
711
712 spawner.spawn(radio_runner_task(lora, mdltn, rx_pkt, tx_pkt).unwrap());
713 }
714
715 let radio_handle = umsh_radio_loraphy::LoraphyRadio::new(&RADIO_CH, t_frame_ms);
717 let crypto = CryptoEngine::new(SoftwareAes, SoftwareSha256);
718 let mut mac = T1000EMac::new(
719 radio_handle,
720 crypto,
721 EmbassyClock,
722 rng,
723 NvmcCounterStore::new(storage),
724 RepeaterConfig::default(),
725 OperatingPolicy::default(),
726 );
727 let identity_id = mac
728 .add_identity(identity)
729 .unwrap_or_else(|_| panic!("identity"));
730 mac.load_persisted_counter(identity_id)
732 .await
733 .unwrap_or_else(|_| panic!("tx counter load"));
734 let mac_cell: &'static AsyncRefCell<T1000EMac> = MAC_CELL.init(AsyncRefCell::new(mac));
735
736 let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs));
738
739 let mut config = Config::new(0x2886, 0x0057);
740 config.manufacturer = Some("Seeed");
741 config.product = Some("T1000-E UMSH CLI");
742 config.serial_number = Some("umsh-t1000e");
743 config.max_power = 100;
744 config.max_packet_size_0 = 64;
745
746 static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new();
747 static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new();
748 static MSOS_DESC: StaticCell<[u8; 0]> = StaticCell::new();
749 static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
750 static STATE: StaticCell<State> = StaticCell::new();
751
752 let mut builder = Builder::new(
753 driver,
754 config,
755 CONFIG_DESC.init([0; 256]),
756 BOS_DESC.init([0; 256]),
757 MSOS_DESC.init([0; 0]),
758 CONTROL_BUF.init([0; 64]),
759 );
760
761 let class = CdcAcmClass::new(&mut builder, STATE.init(State::new()), 64);
762 let mut usb = builder.build();
763
764 let (tx, raw_rx, ctrl) = class.split_with_control();
765 let rx = CdcAcmRescue::new(raw_rx, ctrl);
766
767 let sensor_rail = Output::new(p.P1_06, Level::Low, OutputDrive::Standard);
772 let sensor_enable = Output::new(p.P0_04, Level::Low, OutputDrive::Standard);
773 let external_power = Input::new(p.P0_05, Pull::Down);
774 let charge_active = Input::new(p.P1_03, Pull::Up);
775
776 let handle = MacHandle::new(mac_cell);
783 let mut host: T1000EHost = Host::new(handle);
784 let node = host.add_node(identity_id);
785 let beacon_node = node.clone();
786
787 {
792 let mut peer_buf: heapless::Vec<([u8; 32], Option<heapless::String<16>>), 8> =
793 heapless::Vec::new();
794 let _ = storage.load_all_peers(&mut peer_buf).await;
795 let mut ch_buf: heapless::Vec<(heapless::String<16>, [u8; 32]), 2> =
796 heapless::Vec::new();
797 let _ = storage.load_all_channels(&mut ch_buf).await;
798 for (pk, _alias) in peer_buf.iter() {
799 let _ = node.peer(PublicKey(*pk)).await;
800 }
801 for (name, key_bytes) in ch_buf.iter() {
802 let channel =
803 umsh_node::Channel::private(umsh_core::ChannelKey(*key_bytes), name.as_str());
804 let _ = node.join(&channel).await;
805 }
806 }
807
808 MacHandle::new(mac_cell)
813 .load_all_persisted_rx_counters()
814 .await
815 .ok();
816
817 spawner.spawn(output_task(tx).unwrap());
818 spawner.spawn(button_task(button, storage).unwrap());
819 spawner.spawn(shutdown_task(storage).unwrap());
820 spawner.spawn(
821 power_task(
822 p.SAADC,
823 p.P0_02,
824 p.P0_29,
825 sensor_rail,
826 sensor_enable,
827 external_power,
828 charge_active,
829 )
830 .unwrap(),
831 );
832 spawner.spawn(mac_task(host).unwrap());
833 spawner.spawn(beacon_task(beacon_node).unwrap());
834 spawner
835 .spawn(cli_task(node, local_key, storage, rx, prev_panic_buf, prev_panic_len).unwrap());
836
837 join(usb.run(), heartbeat(led, wdt_handle)).await;
838 }
839}