// Copyright Open Logistics Foundation // // Licensed under the Open Logistics Foundation License 1.3. // For details on the licensing terms, see the LICENSE file. // SPDX-License-Identifier: OLFL-1.3 //! This example attempts to connect to the public leshan lwm2m test server at //! with the endpoint name "coap_zero_observe". //! After registration, it can be controlled at //! . //! //! It will provide one instance of a time object //! (3333, ) //! with the only mandatory field "current time". This object can be accessed at //! . //! //! The example will run and reply to a GET on the current time resource. //! Other requests will be answered with [ServerErrorCode::NotImplemented]. //! //! After 120 seconds, the logical lwm2m connection will be closed (no update implemented in this example), //! but the example will still run until terminated by hand. use std::{fs::File, io::Read, time as stdtime}; use coap_zero::{ endpoint::{outgoing::OutgoingEvent, CoapEndpoint, EndpointEvent, TransmissionParameters}, message::{ codes::RequestCode, options::{CoapOption, CoapOptionName}, Message, }, }; use embedded_timers::clock as embtime; use heapless::Vec; use std_embedded_nal::Stack; #[derive(Debug)] pub struct Random; impl embedded_hal::blocking::rng::Read for Random { type Error = std::io::Error; fn read(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> { File::open("/dev/urandom")?.read_exact(buf) } } #[derive(Debug)] struct SystemClock; impl embtime::Clock for SystemClock { fn try_now(&self) -> Result { stdtime::SystemTime::now() .duration_since(stdtime::SystemTime::UNIX_EPOCH) .map_err(|_| embtime::ClockError::Unknown) } } static CLOCK: SystemClock = SystemClock; fn main() { simple_logger::SimpleLogger::new().env().init().unwrap(); let mut stack = Stack::default(); let mut receive_buffer = [0_u8; coap_zero::DEFAULT_COAP_MESSAGE_SIZE]; let mut endpoint: CoapEndpoint< '_, Stack, Random, SystemClock, 8, // OptionCount 32, // OptionSize 128, // IncomingCommunicationBuffer 128, // OutgoingCommunicationBuffer //{ coap_zero::DEFAULT_COAP_MESSAGE_SIZE }, // ReceiveBuffer > = CoapEndpoint::try_new( TransmissionParameters::default(), Random {}, &CLOCK, &mut receive_buffer, ) .unwrap(); endpoint .connect_to_url(&mut stack, "coap://leshan.eclipseprojects.io:5683") .unwrap(); let option = |name, value| CoapOption { name, value }; let options: heapless::Vec, { coap_zero::DEFAULT_MAX_OPTION_COUNT }> = heapless::Vec::from_slice(&[ option(CoapOptionName::UriPath, b"rd"), option(CoapOptionName::ContentFormat, &[0x28_u8]), option(CoapOptionName::UriQuery, b"lwm2m=1.0"), option(CoapOptionName::UriQuery, b"ep=coap_zero_observe"), option(CoapOptionName::UriQuery, b"lt=120"), ]) .unwrap(); let payload = b""; endpoint .outgoing() .schedule_con( RequestCode::Post, options, Some(payload), stdtime::Duration::from_secs(5), ) .unwrap(); let mut notification_state = None; loop { std::thread::sleep(stdtime::Duration::from_millis(25)); let (in_event, out_event, endpoint_event) = endpoint.process(&mut stack).unwrap(); use coap_zero::endpoint::incoming::IncomingEvent::*; match endpoint_event { EndpointEvent::Nothing => {} ev => log::info!("EndpointEvent: {ev:?}"), } match out_event { Ok(OutgoingEvent::Nothing) => {} Ok(OutgoingEvent::Success(msg)) => { log::info!("OutgoingEvent::Success: {msg:?}"); let msg: Message = msg.try_into().unwrap(); log::info!("{msg:?}"); } Ok(OutgoingEvent::NotificationRst(_token)) => { log::info!("OutgoingEvent::NotificationRst -> Deregister Observe"); notification_state = None; } Ok(ev) => log::info!("OutgoingEvent: {ev:?}"), Err(err) => log::warn!("OutgoingError: {err:?}"), } match in_event { Ok(Request(_confirmable, message)) => { log::info!("incoming state: Received"); log::info!("Received message: {message:?}"); let msg: Message = message.clone().try_into().unwrap(); log::info!("{msg:?}"); drop(msg); let rec_options: std::vec::Vec = message .options_iter() .unwrap() .map(|o| o.unwrap()) .collect(); let uri_path_segments: std::vec::Vec<&CoapOption> = rec_options .iter() .filter(|o| o.name == CoapOptionName::UriPath) .collect(); // We need to check if we get a Get for 3333/0/5506 // Others will be answered with 5.01 (Not Implemented) if uri_path_segments.len() == 3 && uri_path_segments[0].value == b"3333" && uri_path_segments[1].value == b"0" && uri_path_segments[2].value == b"5506" { let now = stdtime::SystemTime::now() .duration_since(stdtime::SystemTime::UNIX_EPOCH) .unwrap(); let payload = format!("{}", now.as_secs()); let mut options: heapless::Vec< CoapOption<'_>, { coap_zero::DEFAULT_MAX_OPTION_COUNT }, > = heapless::Vec::from_slice(&[ option(CoapOptionName::ContentFormat, &[0_u8]), // Text ]) .unwrap(); let observe = rec_options .iter() .filter(|o| o.name == CoapOptionName::Observe) .next(); if let Some(observe) = observe { if observe.value == [] { options.push(option(CoapOptionName::Observe, &[])).unwrap(); notification_state = Some((message.token().unwrap(), stdtime::Instant::now(), 0u32)); log::info!("Observe registered"); } else if observe.value == [1] { notification_state = None; log::info!("Observe deregistered"); } else { panic!("Unexpected observe value"); } } endpoint .incoming() .schedule_response( coap_zero::message::codes::SuccessCode::Content.into(), options, Some(payload.as_bytes()), ) .unwrap(); } else { endpoint .incoming() .schedule_response( coap_zero::message::codes::ServerErrorCode::NotImplemented.into(), Vec::new(), None, ) .unwrap(); } } Ok(Nothing) => {} Ok(ev) => { log::info!("IncomingEvent received: {ev:?}"); } Err(e) => { log::warn!("IncomingError: {e:?}"); } } if let Some((token, last_time, seq_number)) = notification_state { if stdtime::Instant::now().duration_since(last_time) > stdtime::Duration::from_secs(5) { log::info!("Send Notification"); let now = stdtime::SystemTime::now() .duration_since(stdtime::SystemTime::UNIX_EPOCH) .unwrap(); let payload = format!("{}", now.as_secs()); let seq_bytes = seq_number.to_be_bytes(); let options: heapless::Vec< CoapOption<'_>, { coap_zero::DEFAULT_MAX_OPTION_COUNT }, > = heapless::Vec::from_slice(&[ option(CoapOptionName::ContentFormat, &[0_u8]), // Text CoapOption { name: CoapOptionName::Observe, value: &seq_bytes[3..], // single byte sequence number for simplicity }, ]) .unwrap(); let _ = endpoint.outgoing().schedule_notification( false, coap_zero::message::codes::SuccessCode::Content.into(), token, options, Some(payload.as_bytes()), ); notification_state = Some((token, stdtime::Instant::now(), seq_number + 1)); } } } }