1#![cfg_attr(target_os = "none", no_std)]
35#![cfg_attr(target_os = "none", no_main)]
36
37#[cfg(not(target_os = "none"))]
38fn main() {}
39
40#[cfg(target_os = "none")]
41mod panic;
42
43#[cfg(target_os = "none")]
44use umsh_bsp_wio_tracker_l1::display;
45
46#[cfg(target_os = "none")]
47mod cli_io;
48
49#[cfg(target_os = "none")]
50mod defmt_logger {
51 #[defmt::global_logger]
52 struct Logger;
53 unsafe impl defmt::Logger for Logger {
54 fn acquire() {}
55 unsafe fn flush() {}
56 unsafe fn release() {}
57 unsafe fn write(_: &[u8]) {}
58 }
59 defmt::timestamp!("{=u32}", 0u32);
60}
61
62#[cfg(target_os = "none")]
65#[global_allocator]
66static ALLOCATOR: embedded_alloc::Heap = embedded_alloc::Heap::empty();
67
68#[cfg(target_os = "none")]
69mod firmware {
70 use core::sync::atomic::{AtomicU32, Ordering};
71
72 use super::display;
73
74 use embassy_executor::Spawner;
75 use embassy_futures::join::join;
76 use embassy_nrf::bind_interrupts;
77 use embassy_nrf::gpio::{Input, Level, Output, OutputDrive, Pull};
78 use embassy_nrf::nvmc::Nvmc;
79 use embassy_nrf::peripherals;
80 use embassy_nrf::spim::{Config as SpimConfig, Frequency, Spim};
81 use embassy_nrf::twim::{self, Config as TwimConfig, Twim};
82 use embassy_nrf::usb::Driver;
83 use embassy_nrf::usb::vbus_detect::HardwareVbusDetect;
84 use embassy_nrf::wdt::{Config as WdtConfig, Watchdog, WatchdogHandle};
85 use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
86 use embassy_sync::signal::Signal;
87 use embassy_time::{Delay, Instant, Timer};
88 use embassy_usb::class::cdc_acm::{CdcAcmClass, Sender, State};
89 use embassy_usb::{Builder, Config};
90 use embedded_hal_bus::spi::ExclusiveDevice;
91 use lora_phy::LoRa;
92 use lora_phy::iv::GenericSx126xInterfaceVariant;
93 use lora_phy::mod_params::{Bandwidth, ModulationParams, PacketParams, SpreadingFactor};
94 use lora_phy::sx126x::{Config as LoraConfig, Sx126x, Sx1262, TcxoCtrlVoltage};
95 use static_cell::StaticCell;
96 use umsh_bsp_nrf52840::cdc_rescue::CdcAcmRescue;
97 use umsh_bsp_nrf52840::flash_store;
98 use umsh_bsp_nrf52840::flash_store::{
99 NvmcChannelStore, NvmcCounterStore, NvmcPeerStore, NvmcStorage,
100 };
101 use umsh_bsp_nrf52840::panic_persist::PanicSlot;
102 use umsh_bsp_nrf52840::{EmbassyClock, Nrf52840Rng};
103 use umsh_bsp_wio_tracker_l1::{PowerSignaler, WioMac, WioTrackerPlatform};
104 use umsh_core::{ChannelKey, PayloadType, PublicKey};
105 use umsh_crypto::{
106 CryptoEngine, NodeIdentity,
107 software::{SoftwareAes, SoftwareIdentity, SoftwareSha256},
108 };
109 use umsh_mac::{LocalIdentityId, MacHandle, OperatingPolicy, RepeaterConfig};
110 use umsh_node::{Channel, Host, LocalNode};
111 use umsh_sync::AsyncRefCell;
112
113 use super::cli_io;
114 use umsh_ux_tracker::led::{LedEngine, LedTimings};
115
116 bind_interrupts!(struct Irqs {
117 USBD => embassy_nrf::usb::InterruptHandler<peripherals::USBD>;
118 CLOCK_POWER => embassy_nrf::usb::vbus_detect::InterruptHandler;
119 TWISPI0 => embassy_nrf::twim::InterruptHandler<peripherals::TWISPI0>;
120 TWISPI1 => embassy_nrf::spim::InterruptHandler<peripherals::TWISPI1>;
121 });
122
123 const TX_POWER_DBM: i32 = 14;
126
127 type RadioSpiBus = ExclusiveDevice<Spim<'static>, Output<'static>, Delay>;
130 type RadioIv = GenericSx126xInterfaceVariant<Output<'static>, Input<'static>>;
131 type RadioKind = Sx126x<RadioSpiBus, RadioIv, Sx1262>;
132 type LoraRadio = LoRa<RadioKind, Delay>;
133
134 type WioHost = Host<MacHandle<'static, WioTrackerPlatform, 2, 8, 4, 4, 8, 255, 32>>;
138 type WioNode = LocalNode<MacHandle<'static, WioTrackerPlatform, 2, 8, 4, 4, 8, 255, 32>>;
140
141 type WioUsbDriver = Driver<'static, HardwareVbusDetect>;
151 type WioSender = Sender<'static, WioUsbDriver>;
152 type WioRescue = CdcAcmRescue<'static, WioUsbDriver>;
153
154 type RadioCh = umsh_radio_loraphy::Channels<ThreadModeRawMutex, 4, 2>;
157 static RADIO_CH: RadioCh = RadioCh::new();
158
159 static PACKET_COUNT: AtomicU32 = AtomicU32::new(0);
160 static DISPLAY_SIGNAL: Signal<ThreadModeRawMutex, ()> = Signal::new();
161
162 static MAC_CELL: StaticCell<AsyncRefCell<WioMac>> = StaticCell::new();
169 static STORAGE: StaticCell<NvmcStorage> = StaticCell::new();
170
171 static IDENTITY_SIGNAL: Signal<ThreadModeRawMutex, ([u8; 32], [u8; 256], usize)> =
174 Signal::new();
175
176 #[embassy_executor::task]
179 async fn display_task(i2c: Twim<'static>) {
180 use embedded_graphics::Drawable;
181 use embedded_graphics::geometry::Point;
182 use embedded_graphics::mono_font::MonoTextStyle;
183 use embedded_graphics::mono_font::ascii::FONT_6X10;
184 use embedded_graphics::pixelcolor::BinaryColor;
185 use embedded_graphics::text::{Baseline, Text};
186 use heapless::String;
187
188 let mut oled = display::Sh1106::new(i2c);
189 oled.init().await;
190
191 let sha = env!("GIT_SHORT_SHA");
192 let style = MonoTextStyle::new(&FONT_6X10, BinaryColor::On);
193
194 let render = |fb: &mut display::Sh1106Fb, count: u32| {
195 fb.clear();
196 let _ = Text::with_baseline("UMSH bringup", Point::new(0, 0), style, Baseline::Top)
197 .draw(fb);
198 let _ = Text::with_baseline(sha, Point::new(0, 16), style, Baseline::Top).draw(fb);
199 let mut s: String<16> = String::new();
200 let _ = core::fmt::write(&mut s, format_args!("MAC: {}", count));
201 let _ = Text::with_baseline(&s, Point::new(0, 32), style, Baseline::Top).draw(fb);
202 };
203
204 let mut fb = display::Sh1106Fb::new();
205 render(&mut fb, 0);
206 oled.flush(&fb).await;
207
208 loop {
209 DISPLAY_SIGNAL.wait().await;
210 let count = PACKET_COUNT.load(Ordering::Relaxed);
211 render(&mut fb, count);
212 oled.flush(&fb).await;
213 }
214 }
215
216 #[embassy_executor::task]
217 async fn radio_runner_task(
218 lora: LoraRadio,
219 mdltn: ModulationParams,
220 rx_pkt: PacketParams,
221 tx_pkt: PacketParams,
222 ) {
223 umsh_radio_loraphy::runner(lora, &RADIO_CH, mdltn, rx_pkt, tx_pkt, TX_POWER_DBM, None)
224 .await;
225 }
226
227 #[embassy_executor::task]
236 async fn identity_persist_task(storage: &'static NvmcStorage) {
237 loop {
238 let (pk, payload, len) = IDENTITY_SIGNAL.wait().await;
239 if storage.peer_exists(&pk).await.unwrap_or(false) {
240 let _ = storage.update_peer_identity(&pk, &payload[..len]).await;
241 }
242 }
243 }
244
245 #[embassy_executor::task]
250 async fn output_task(mut tx: WioSender) {
251 cli_io::drain_to_sender(&mut tx).await;
252 }
253
254 #[embassy_executor::task]
258 async fn mac_task(mut host: WioHost, identity_id: LocalIdentityId) {
259 let sub_node = host.node(identity_id).expect("node just added");
262 let _identity_sub = sub_node.on_receive(|pkt| {
263 if pkt.payload_type() != PayloadType::NodeIdentity {
264 return false;
265 }
266 let Some(from) = pkt.from_key() else {
267 return false;
268 };
269 let raw = pkt.payload();
270 let len = raw.len().min(256);
271 let mut buf = [0u8; 256];
272 buf[..len].copy_from_slice(&raw[..len]);
273 IDENTITY_SIGNAL.signal((from.0, buf, len));
274 false
275 });
276
277 let _ = host.run().await;
278 panic!("host exited");
279 }
280
281 #[embassy_executor::task]
285 async fn cli_task(
286 node: WioNode,
287 local_key: PublicKey,
288 storage: &'static NvmcStorage,
289 rx: WioRescue,
290 prev_panic_buf: &'static [u8; 256],
291 prev_panic_len: usize,
292 ) {
293 use umsh_cli::CliSession;
294 use umsh_cli::io::CliOutput;
295 use umsh_cli::logger::NullLogger;
296
297 let mut input = cli_io::CdcInput::new(rx);
298 let mut out = cli_io::CdcOutput::new();
299
300 input.wait_connection().await;
303
304 let _ = out.write_line("").await;
305 let _ = out.write_line("UMSH CLI (Wio Tracker L1)").await;
306 let _ = out.write_line("type /help for commands").await;
307 if prev_panic_len > 0 {
308 let _ = out.write_line("[PREV PANIC]:").await;
309 if let Ok(s) = core::str::from_utf8(&prev_panic_buf[..prev_panic_len]) {
310 let _ = out.write_line(s).await;
311 }
312 }
313
314 let peer_store = NvmcPeerStore::new(storage);
315 let channel_store = NvmcChannelStore::new(storage);
316 let mut cli: CliSession<_, _, _, _, _, _, 4, 4, 2, 8, 128> = CliSession::new(
317 node,
318 local_key,
319 out,
320 NullLogger::new(),
321 peer_store,
322 channel_store,
323 PowerSignaler,
324 );
325
326 let _ = cli.run(&mut input).await;
329 panic!("cli exited");
330 }
331
332 #[embassy_executor::main]
335 async fn main(spawner: Spawner) {
336 {
340 use core::mem::MaybeUninit;
341 const HEAP_SIZE: usize = 8192;
342 static mut HEAP: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
343 unsafe { crate::ALLOCATOR.init(core::ptr::addr_of!(HEAP) as usize, HEAP_SIZE) }
344 }
345
346 let p = embassy_nrf::init(umsh_bsp_nrf52840::clocks::default_config());
347
348 let mut wdt_config = WdtConfig::default();
349 wdt_config.timeout_ticks = 32768 * 8;
350 let (_wdt, [wdt_handle]) =
351 Watchdog::try_new::<_, 1>(p.WDT, wdt_config).unwrap_or_else(|_| panic!("wdt"));
352
353 static PREV_PANIC_BUF: StaticCell<[u8; 256]> = StaticCell::new();
356 let mut prev_panic_tmp = [0u8; 256];
357 let prev_panic_len = {
358 let mut slot = PanicSlot::new(super::panic::panic_region());
359 if let Some(msg) = slot.read() {
360 let n = msg.len().min(prev_panic_tmp.len());
361 prev_panic_tmp[..n].copy_from_slice(&msg[..n]);
362 slot.clear();
363 n
364 } else {
365 0
366 }
367 };
368 let prev_panic_buf: &'static [u8; 256] = PREV_PANIC_BUF.init(prev_panic_tmp);
369
370 {
372 static TWIM0_BUF: StaticCell<[u8; 256]> = StaticCell::new();
373 let mut twim_cfg = TwimConfig::default();
374 twim_cfg.frequency = twim::Frequency::K400;
375 let i2c = Twim::new(
376 p.TWISPI0,
377 Irqs,
378 p.P0_06,
379 p.P0_05,
380 twim_cfg,
381 TWIM0_BUF.init([0; 256]),
382 );
383 spawner.spawn(display_task(i2c).unwrap());
384 }
385
386 let t_frame_ms = umsh_radio_loraphy::airtime_ms(
388 SpreadingFactor::_7,
389 Bandwidth::_62KHz,
390 umsh_radio_loraphy::MAX_PAYLOAD,
391 );
392 {
393 let mut spi_cfg = SpimConfig::default();
394 spi_cfg.frequency = Frequency::M16;
395 let radio_bus = Spim::new(
396 p.TWISPI1, Irqs, p.P0_30, p.P0_03, p.P0_28, spi_cfg,
400 );
401 let radio_cs = Output::new(p.P1_14, Level::High, OutputDrive::Standard);
402 let radio_spi = ExclusiveDevice::new(radio_bus, radio_cs, Delay).unwrap();
403
404 let radio_rst = Output::new(p.P1_07, Level::High, OutputDrive::Standard);
405 let radio_dio1 = Input::new(p.P0_07, Pull::None);
406 let radio_busy = Input::new(p.P1_10, Pull::None);
407 let radio_rxen = Output::new(p.P1_08, Level::Low, OutputDrive::Standard);
408
409 let iv = GenericSx126xInterfaceVariant::new(
410 radio_rst,
411 radio_dio1,
412 radio_busy,
413 Some(radio_rxen), None, )
416 .unwrap();
417
418 let lora_config = LoraConfig {
419 chip: Sx1262,
420 tcxo_ctrl: Some(TcxoCtrlVoltage::Ctrl1V8),
421 use_dcdc: true,
422 rx_boost: true,
423 };
424
425 let mut lora = LoRa::new(Sx126x::new(radio_spi, iv, lora_config), false, Delay)
426 .await
427 .unwrap_or_else(|_| panic!("radio init"));
428
429 let (mdltn, rx_pkt, tx_pkt) = umsh_radio_loraphy::profile_params(
430 &mut lora,
431 umsh_radio_loraphy::profiles::DEFAULT,
432 8,
433 )
434 .unwrap_or_else(|_| panic!("radio params"));
435
436 spawner.spawn(radio_runner_task(lora, mdltn, rx_pkt, tx_pkt).unwrap());
437 }
438
439 let storage: &'static NvmcStorage =
441 STORAGE.init(flash_store::new_storage(Nvmc::new(p.NVMC)));
442
443 let mut rng = Nrf52840Rng::new(p.RNG);
452 let sk_bytes: [u8; 32] = match storage.load_sk().await {
453 Ok(Some(sk)) => sk,
454 Ok(None) => {
455 let mut sk = [0u8; 32];
456 rng.fill_bytes(&mut sk);
457 storage
458 .store_sk(&sk)
459 .await
460 .unwrap_or_else(|_| panic!("identity persist"));
461 sk
462 }
463 Err(_) => panic!("storage init failed"),
464 };
465 let identity = SoftwareIdentity::from_secret_bytes(&sk_bytes);
466 let local_key = *identity.public_key();
467
468 let radio_handle = umsh_radio_loraphy::LoraphyRadio::new(&RADIO_CH, t_frame_ms);
469 let crypto = CryptoEngine::new(SoftwareAes, SoftwareSha256);
470 let mut mac = WioMac::new(
471 radio_handle,
472 crypto,
473 EmbassyClock,
474 rng,
475 NvmcCounterStore::new(storage),
476 RepeaterConfig::default(),
477 OperatingPolicy::default(),
478 );
479 let identity_id = mac
480 .add_identity(identity)
481 .unwrap_or_else(|_| panic!("identity"));
482 mac.load_persisted_counter(identity_id)
483 .await
484 .unwrap_or_else(|_| panic!("tx counter load"));
485
486 let mac_cell: &'static AsyncRefCell<WioMac> = MAC_CELL.init(AsyncRefCell::new(mac));
489
490 let handle = MacHandle::new(mac_cell);
498 let mut host: WioHost = Host::new(handle);
499 let node = host.add_node(identity_id);
500
501 {
502 let mut peer_buf: heapless::Vec<([u8; 32], Option<heapless::String<16>>), 8> =
503 heapless::Vec::new();
504 let _ = storage.load_all_peers(&mut peer_buf).await;
505 let mut ch_buf: heapless::Vec<(heapless::String<16>, [u8; 32]), 2> =
506 heapless::Vec::new();
507 let _ = storage.load_all_channels(&mut ch_buf).await;
508 for (pk, _alias) in peer_buf.iter() {
509 let _ = node.peer(PublicKey(*pk)).await;
510 }
511 for (name, key_bytes) in ch_buf.iter() {
512 let channel = Channel::private(ChannelKey(*key_bytes), name.as_str());
513 let _ = node.join(&channel).await;
514 }
515 }
516 MacHandle::new(mac_cell)
519 .load_all_persisted_rx_counters()
520 .await
521 .ok();
522
523 let led = Output::new(p.P1_01, Level::Low, OutputDrive::Standard);
525 let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs));
526
527 let mut config = Config::new(0x2886, 0x1667);
528 config.manufacturer = Some("UMSH");
529 config.product = Some("Seeed Wio Tracker L1 Bringup");
530 config.serial_number = Some("wio-tracker-l1-console");
531 config.max_power = 100;
532 config.max_packet_size_0 = 64;
533
534 static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new();
535 static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new();
536 static MSOS_DESC: StaticCell<[u8; 0]> = StaticCell::new();
537 static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
538 static STATE: StaticCell<State> = StaticCell::new();
539
540 let mut builder = Builder::new(
541 driver,
542 config,
543 CONFIG_DESC.init([0; 256]),
544 BOS_DESC.init([0; 256]),
545 MSOS_DESC.init([0; 0]),
546 CONTROL_BUF.init([0; 64]),
547 );
548
549 let class = CdcAcmClass::new(&mut builder, STATE.init(State::new()), 64);
550 let mut usb = builder.build();
551
552 let (tx, raw_rx, ctrl) = class.split_with_control();
553 let rx = CdcAcmRescue::new(raw_rx, ctrl);
554
555 spawner.spawn(output_task(tx).unwrap());
556 spawner.spawn(identity_persist_task(storage).unwrap());
557 spawner.spawn(mac_task(host, identity_id).unwrap());
558 spawner
559 .spawn(cli_task(node, local_key, storage, rx, prev_panic_buf, prev_panic_len).unwrap());
560
561 join(usb.run(), heartbeat(led, wdt_handle)).await;
562 }
563
564 async fn heartbeat(mut led: Output<'static>, mut wdt: WatchdogHandle) -> ! {
567 let mut engine = LedEngine::new(LedTimings::default(), Instant::now().as_millis());
568 loop {
569 wdt.pet();
570 let decision = engine.tick(Instant::now().as_millis());
571 if decision.on {
572 led.set_high()
573 } else {
574 led.set_low()
575 }
576 Timer::at(Instant::from_millis(decision.next_deadline_ms)).await;
577 }
578 }
579}