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).await;
224 }
225
226 #[embassy_executor::task]
235 async fn identity_persist_task(storage: &'static NvmcStorage) {
236 loop {
237 let (pk, payload, len) = IDENTITY_SIGNAL.wait().await;
238 if storage.peer_exists(&pk).await.unwrap_or(false) {
239 let _ = storage.update_peer_identity(&pk, &payload[..len]).await;
240 }
241 }
242 }
243
244 #[embassy_executor::task]
249 async fn output_task(mut tx: WioSender) {
250 cli_io::drain_to_sender(&mut tx).await;
251 }
252
253 #[embassy_executor::task]
257 async fn mac_task(mut host: WioHost, identity_id: LocalIdentityId) {
258 let sub_node = host.node(identity_id).expect("node just added");
261 let _identity_sub = sub_node.on_receive(|pkt| {
262 if pkt.payload_type() != PayloadType::NodeIdentity {
263 return false;
264 }
265 let Some(from) = pkt.from_key() else {
266 return false;
267 };
268 let raw = pkt.payload();
269 let len = raw.len().min(256);
270 let mut buf = [0u8; 256];
271 buf[..len].copy_from_slice(&raw[..len]);
272 IDENTITY_SIGNAL.signal((from.0, buf, len));
273 false
274 });
275
276 let _ = host.run().await;
277 panic!("host exited");
278 }
279
280 #[embassy_executor::task]
284 async fn cli_task(
285 node: WioNode,
286 local_key: PublicKey,
287 storage: &'static NvmcStorage,
288 rx: WioRescue,
289 prev_panic_buf: &'static [u8; 256],
290 prev_panic_len: usize,
291 ) {
292 use umsh_cli::CliSession;
293 use umsh_cli::io::CliOutput;
294 use umsh_cli::logger::NullLogger;
295
296 let mut input = cli_io::CdcInput::new(rx);
297 let mut out = cli_io::CdcOutput::new();
298
299 input.wait_connection().await;
302
303 let _ = out.write_line("").await;
304 let _ = out.write_line("UMSH CLI (Wio Tracker L1)").await;
305 let _ = out.write_line("type /help for commands").await;
306 if prev_panic_len > 0 {
307 let _ = out.write_line("[PREV PANIC]:").await;
308 if let Ok(s) = core::str::from_utf8(&prev_panic_buf[..prev_panic_len]) {
309 let _ = out.write_line(s).await;
310 }
311 }
312
313 let peer_store = NvmcPeerStore::new(storage);
314 let channel_store = NvmcChannelStore::new(storage);
315 let mut cli: CliSession<_, _, _, _, _, _, 4, 4, 2, 8, 128> = CliSession::new(
316 node,
317 local_key,
318 out,
319 NullLogger::new(),
320 peer_store,
321 channel_store,
322 PowerSignaler,
323 );
324
325 let _ = cli.run(&mut input).await;
328 panic!("cli exited");
329 }
330
331 #[embassy_executor::main]
334 async fn main(spawner: Spawner) {
335 {
339 use core::mem::MaybeUninit;
340 const HEAP_SIZE: usize = 8192;
341 static mut HEAP: [MaybeUninit<u8>; HEAP_SIZE] = [MaybeUninit::uninit(); HEAP_SIZE];
342 unsafe { crate::ALLOCATOR.init(core::ptr::addr_of!(HEAP) as usize, HEAP_SIZE) }
343 }
344
345 let p = embassy_nrf::init(umsh_bsp_nrf52840::clocks::default_config());
346
347 let mut wdt_config = WdtConfig::default();
348 wdt_config.timeout_ticks = 32768 * 8;
349 let (_wdt, [wdt_handle]) =
350 Watchdog::try_new::<_, 1>(p.WDT, wdt_config).unwrap_or_else(|_| panic!("wdt"));
351
352 static PREV_PANIC_BUF: StaticCell<[u8; 256]> = StaticCell::new();
355 let mut prev_panic_tmp = [0u8; 256];
356 let prev_panic_len = {
357 let mut slot = PanicSlot::new(super::panic::panic_region());
358 if let Some(msg) = slot.read() {
359 let n = msg.len().min(prev_panic_tmp.len());
360 prev_panic_tmp[..n].copy_from_slice(&msg[..n]);
361 slot.clear();
362 n
363 } else {
364 0
365 }
366 };
367 let prev_panic_buf: &'static [u8; 256] = PREV_PANIC_BUF.init(prev_panic_tmp);
368
369 {
371 static TWIM0_BUF: StaticCell<[u8; 256]> = StaticCell::new();
372 let mut twim_cfg = TwimConfig::default();
373 twim_cfg.frequency = twim::Frequency::K400;
374 let i2c = Twim::new(
375 p.TWISPI0,
376 Irqs,
377 p.P0_06,
378 p.P0_05,
379 twim_cfg,
380 TWIM0_BUF.init([0; 256]),
381 );
382 spawner.spawn(display_task(i2c).unwrap());
383 }
384
385 let t_frame_ms = umsh_radio_loraphy::airtime_ms(
387 SpreadingFactor::_7,
388 Bandwidth::_62KHz,
389 umsh_radio_loraphy::MAX_PAYLOAD,
390 );
391 {
392 let mut spi_cfg = SpimConfig::default();
393 spi_cfg.frequency = Frequency::M16;
394 let radio_bus = Spim::new(
395 p.TWISPI1, Irqs, p.P0_30, p.P0_03, p.P0_28, spi_cfg,
399 );
400 let radio_cs = Output::new(p.P1_14, Level::High, OutputDrive::Standard);
401 let radio_spi = ExclusiveDevice::new(radio_bus, radio_cs, Delay).unwrap();
402
403 let radio_rst = Output::new(p.P1_07, Level::High, OutputDrive::Standard);
404 let radio_dio1 = Input::new(p.P0_07, Pull::None);
405 let radio_busy = Input::new(p.P1_10, Pull::None);
406 let radio_rxen = Output::new(p.P1_08, Level::Low, OutputDrive::Standard);
407
408 let iv = GenericSx126xInterfaceVariant::new(
409 radio_rst,
410 radio_dio1,
411 radio_busy,
412 Some(radio_rxen), None, )
415 .unwrap();
416
417 let lora_config = LoraConfig {
418 chip: Sx1262,
419 tcxo_ctrl: Some(TcxoCtrlVoltage::Ctrl1V8),
420 use_dcdc: true,
421 rx_boost: true,
422 };
423
424 let mut lora = LoRa::new(Sx126x::new(radio_spi, iv, lora_config), false, Delay)
425 .await
426 .unwrap_or_else(|_| panic!("radio init"));
427
428 let (mdltn, rx_pkt, tx_pkt) = umsh_radio_loraphy::meshcore_us_params(&mut lora)
429 .unwrap_or_else(|_| panic!("radio params"));
430
431 spawner.spawn(radio_runner_task(lora, mdltn, rx_pkt, tx_pkt).unwrap());
432 }
433
434 let storage: &'static NvmcStorage =
436 STORAGE.init(flash_store::new_storage(Nvmc::new(p.NVMC)));
437
438 let mut rng = Nrf52840Rng::new(p.RNG);
447 let sk_bytes: [u8; 32] = match storage.load_sk().await {
448 Ok(Some(sk)) => sk,
449 Ok(None) => {
450 let mut sk = [0u8; 32];
451 rng.fill_bytes(&mut sk);
452 storage
453 .store_sk(&sk)
454 .await
455 .unwrap_or_else(|_| panic!("identity persist"));
456 sk
457 }
458 Err(_) => panic!("storage init failed"),
459 };
460 let identity = SoftwareIdentity::from_secret_bytes(&sk_bytes);
461 let local_key = *identity.public_key();
462
463 let radio_handle = umsh_radio_loraphy::LoraphyRadio::new(&RADIO_CH, t_frame_ms);
464 let crypto = CryptoEngine::new(SoftwareAes, SoftwareSha256);
465 let mut mac = WioMac::new(
466 radio_handle,
467 crypto,
468 EmbassyClock,
469 rng,
470 NvmcCounterStore::new(storage),
471 RepeaterConfig::default(),
472 OperatingPolicy::default(),
473 );
474 let identity_id = mac
475 .add_identity(identity)
476 .unwrap_or_else(|_| panic!("identity"));
477 mac.load_persisted_counter(identity_id)
478 .await
479 .unwrap_or_else(|_| panic!("tx counter load"));
480
481 let mac_cell: &'static AsyncRefCell<WioMac> = MAC_CELL.init(AsyncRefCell::new(mac));
484
485 let handle = MacHandle::new(mac_cell);
493 let mut host: WioHost = Host::new(handle);
494 let node = host.add_node(identity_id);
495
496 {
497 let mut peer_buf: heapless::Vec<([u8; 32], Option<heapless::String<16>>), 8> =
498 heapless::Vec::new();
499 let _ = storage.load_all_peers(&mut peer_buf).await;
500 let mut ch_buf: heapless::Vec<(heapless::String<16>, [u8; 32]), 2> =
501 heapless::Vec::new();
502 let _ = storage.load_all_channels(&mut ch_buf).await;
503 for (pk, _alias) in peer_buf.iter() {
504 let _ = node.peer(PublicKey(*pk)).await;
505 }
506 for (name, key_bytes) in ch_buf.iter() {
507 let channel = Channel::private(ChannelKey(*key_bytes), name.as_str());
508 let _ = node.join(&channel).await;
509 }
510 }
511 MacHandle::new(mac_cell)
514 .load_all_persisted_rx_counters()
515 .await
516 .ok();
517
518 let led = Output::new(p.P1_01, Level::Low, OutputDrive::Standard);
520 let driver = Driver::new(p.USBD, Irqs, HardwareVbusDetect::new(Irqs));
521
522 let mut config = Config::new(0x2886, 0x1667);
523 config.manufacturer = Some("UMSH");
524 config.product = Some("Seeed Wio Tracker L1 Bringup");
525 config.serial_number = Some("wio-tracker-l1-console");
526 config.max_power = 100;
527 config.max_packet_size_0 = 64;
528
529 static CONFIG_DESC: StaticCell<[u8; 256]> = StaticCell::new();
530 static BOS_DESC: StaticCell<[u8; 256]> = StaticCell::new();
531 static MSOS_DESC: StaticCell<[u8; 0]> = StaticCell::new();
532 static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
533 static STATE: StaticCell<State> = StaticCell::new();
534
535 let mut builder = Builder::new(
536 driver,
537 config,
538 CONFIG_DESC.init([0; 256]),
539 BOS_DESC.init([0; 256]),
540 MSOS_DESC.init([0; 0]),
541 CONTROL_BUF.init([0; 64]),
542 );
543
544 let class = CdcAcmClass::new(&mut builder, STATE.init(State::new()), 64);
545 let mut usb = builder.build();
546
547 let (tx, raw_rx, ctrl) = class.split_with_control();
548 let rx = CdcAcmRescue::new(raw_rx, ctrl);
549
550 spawner.spawn(output_task(tx).unwrap());
551 spawner.spawn(identity_persist_task(storage).unwrap());
552 spawner.spawn(mac_task(host, identity_id).unwrap());
553 spawner
554 .spawn(cli_task(node, local_key, storage, rx, prev_panic_buf, prev_panic_len).unwrap());
555
556 join(usb.run(), heartbeat(led, wdt_handle)).await;
557 }
558
559 async fn heartbeat(mut led: Output<'static>, mut wdt: WatchdogHandle) -> ! {
562 let mut engine = LedEngine::new(LedTimings::default(), Instant::now().as_millis());
563 loop {
564 wdt.pet();
565 let decision = engine.tick(Instant::now().as_millis());
566 if decision.on {
567 led.set_high()
568 } else {
569 led.set_low()
570 }
571 Timer::at(Instant::from_millis(decision.next_deadline_ms)).await;
572 }
573 }
574}