#![no_std] #![no_main] #![allow(async_fn_in_trait)] extern crate alloc; use arrayvec::{ArrayString, ArrayVec}; use defmt::{info, Debug2Format}; use core::{convert::Infallible, fmt::Write, str::FromStr}; use embassy_sync::{ blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, pubsub::PubSubChannel, }; use embassy_time::Timer; use embedded_graphics::{ draw_target::DrawTarget, geometry::OriginDimensions, mono_font::{ascii::FONT_4X6, MonoTextStyle}, pixelcolor::Gray4, prelude::*, primitives::{Circle, PrimitiveStyle, Rectangle}, text::{Alignment, Text}, }; use embedded_hal::digital::StatefulOutputPin; use lora_phy::{mod_traits::RadioKind, DelayNs, LoRa}; use meshcore::{ crypto::{PrivateKey, PublicKey, SharedSecret}, packet::Packet, packet_content::PacketContent, text::ClearText, }; pub static MESSAGE_CH: PubSubChannel = PubSubChannel::new(); pub async fn led_task( led_pin: &Mutex>, ) { let mut subscriber = MESSAGE_CH.subscriber().unwrap(); loop { let _ = subscriber.next_message().await; info!("led: got message"); let mut led = led_pin.lock().await; for _ in 0..3 { led.set_low().unwrap(); Timer::after_millis(50).await; led.set_high().unwrap(); Timer::after_millis(50).await; } } } fn cstr(bytes: &[u8]) -> &str { let end = bytes.iter().position(|&b| b == 0).unwrap_or(bytes.len()); core::str::from_utf8(&bytes[..end]).unwrap_or("") } pub async fn display_task(display: &mut D, mut finish: impl AsyncFnMut(&mut D)) where D: DrawTarget + OriginDimensions, ::Error: core::fmt::Debug, { let radius: u16 = 10; let mut pos = Point::new(30, 30); let mut vel = Point::new(-2, 1); let pp = Point::new(radius.into(), radius.into()); let bounds = Rectangle::with_corners(Point::new(0, 0), -(pp - display.size())); let mut lines = ArrayVec::, 8>::new(); let mut subscriber = MESSAGE_CH.subscriber().unwrap(); loop { // update ball pos += vel; let bottom_right = bounds.bottom_right().unwrap(); if pos.x < bounds.top_left.x || pos.x > bottom_right.x { vel.x = -vel.x; } if pos.y < bounds.top_left.y || pos.y > bottom_right.y { vel.y = -vel.y; } pos.x = pos.x.clamp(bounds.top_left.x, bottom_right.x); pos.y = pos.y.clamp(bounds.top_left.y, bottom_right.y); // update chat if let Some(msg) = subscriber.try_next_message_pure() { info!("display: got message"); if lines.is_full() { lines.remove(0); } let mut str: ArrayString<128> = ArrayString::new(); write!(&mut str, "{}: {}", cstr(&msg.sender), cstr(&msg.message)).unwrap(); lines.push(str); } display.clear(Gray4::BLACK).unwrap(); Circle::new(pos, radius.into()) .into_styled(PrimitiveStyle::with_stroke(Gray4::new(4), 3)) .draw(display) .unwrap(); for (i, line) in lines.iter().enumerate() { Text::with_alignment( line, Point::new(4, 4 + 8 * (i as i32)), MonoTextStyle::new(&FONT_4X6, Gray4::WHITE), Alignment::Left, ) .draw(display) .unwrap(); } finish(display).await; Timer::after_millis(10).await; } } fn hex16(hexstr: &'static str) -> [u8; 16] { let mut buf = [0u8; 16]; hex::decode_to_slice(hexstr, &mut buf).expect("invalid hex secret"); buf } pub async fn radio_task(lora: &mut LoRa) { use lora_phy::mod_params::*; let keystore = meshcore::no_std_identity::StaticKeystoreInput { identities: [PrivateKey::from_str( "5884BEECFA94A15B596C8FE51832B857EB018B37F3E85671971977F5A6139374B9C3ECD2415258A9C233D4CA9332C7BF06EB269B786EA71813DE17DFFF480177", ) .unwrap()] .into_iter() .collect(), contacts: [].into_iter().collect(), groups: [ SharedSecret::new_from_group_secret(&hex16("8b3387e9c5cdea6ac9e5edbaa115cd72")), SharedSecret::new_from_group_secret(&hex16("4c97bfe431abe20672259d9b6969dcec")), ] .into_iter() .collect(), } .compile(); let pk: PublicKey = (&keystore.identities[0].private_key).into(); info!("public key: {:?}", Debug2Format(&pk)); let publisher = MESSAGE_CH.publisher().unwrap(); let mut buffer: [u8; 256] = [0; 256]; let mdltn_params = lora .create_modulation_params( SpreadingFactor::_8, Bandwidth::_62KHz, CodingRate::_4_8, 869_618_000, ) .unwrap(); let rx_params = lora .create_rx_packet_params(16, false, buffer.len() as u8, true, false, &mdltn_params) .unwrap(); lora.prepare_for_rx(RxMode::Continuous, &mdltn_params, &rx_params) .await .unwrap(); loop { info!("waiting for message"); let res = lora.rx(&rx_params, &mut buffer).await.unwrap(); let message = &buffer[0..res.0.into()]; let packet = Packet::try_from(message).unwrap(); info!("packet {:?}", Debug2Format(&packet)); match packet.content { PacketContent::Advert(msg) => { info!("got advert from {}", &msg.name); } PacketContent::GroupText(mut msg) => { msg.decrypt(&keystore).unwrap(); if let Some(text) = msg.cleartext { unsafe { info!( "decrypted group message from {}: {}", alloc::str::from_utf8_unchecked(&text.sender), alloc::str::from_utf8_unchecked(&text.message), ); } publisher.publish(text).await; } else { info!("failed to decrypt group message"); } } PacketContent::Text(mut msg) => { msg.decrypt(&keystore).unwrap(); if let Some(text) = msg.cleartext { unsafe { info!( "decrypted p2p message from {}: {}", alloc::str::from_utf8_unchecked(&text.sender), alloc::str::from_utf8_unchecked(&text.message), ); } publisher.publish(text).await; } else { info!("failed to decrypt p2p message"); } } content => { info!("content: {:?}", Debug2Format(&content)); } } } }