// 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"3333/0>";
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));
}
}
}
}