coap-message-demos 0.5.0

Demos of the coap-message ecosystem
Documentation
//! This crate contains demo applications for CoAP on Rust
//!
//! All demos use the ecosystem around the [coap-message] crate. They come in two variations:
//!
//! * "applications" contain code that would typically be the high-level code that executes
//!   business logic.
//!
//!   They are a mix of standalone resource implementations, collections thereof into a
//!   whole-server handler, and possibly client code.
//!
//!   They reside in the `src/` directory, and are available as library modules. This allows
//!   integrating them into other demo code, eg. into examples of a coap-message implementation.
//!
//! * "examples" are the stand-alone executable binaries using various backends.
//!
//!   They pick suitable applications, and wrap them with a CoAP implementation of choice into a
//!   program that can be run with `cargo run --example X`.
//!
//!   Currently, the examples in this crate show the use of:
//!
//!   * [coap-lite](https://crates.io/crates/coap-lite),
//!     a building block for CoAP-over-UDP libraries, running directly on a socket in the example.
//!
//!   * [the coap crate](https://crates.io/crates/coap),
//!     which provides a full implementation, and can interface with coap-message by virtue of
//!     using coap-lite as a backend.
//!
//!   * [embedded-nal-minimal-coapserver](https://crates.io/crates/embedded-nal-minimal-coapserver),
//!     which implements CoAP-over-UDP on the Embedded Network Abstraction Layer
//!     and processes messages through the [coap_handler] types.
//!     For the example, it uses a std implementation of embedded-nal.
//!
//!   Examples that need larger ecosystem support and can not simply be launched natively by `cargo
//!   run --example` are not included here, but show (maybe even better) what the coap-message
//!   ecosystem is capable of providing:
//!
//!   * [verdigris](https://crates.io/crates/verdigris)
//!     is an implementation of CoAP that runs in the web browser and uses CoAP-over-WebSockets.
//!     It includes the demo applications in its color server sub-application, where they can be
//!     accessed through a proxying Resource Directory.
//!
//!   * [RIOT](https://riot-os.org/) is an embedded operating system for the Internet of Things.
//!     In its [rust-gcoap example](https://github.com/RIOT-OS/RIOT/tree/master/examples/rust-gcoap),
//!     the application runs the no_std part of the demo applications on RIOT's own gcoap
//!     implementation.
//!
//! Usage
//! -----
//!
//! The examples are all configured to run a selection of the applications; which they are depends
//! on the selected features.
//!
//! For minimal operation, run the examples as
//!
//! ```sh
//! $ cargo run --example EXNAME --features example-EXNAME
//! ```
//!
//! where `EXNAME` is substituted with any of the examples -- currently `coaplite`, `coap_crate` or
//! `std_embedded_nal_minicoapserver`.
//!
//! To explore all features, just run with
//!
//! ```sh
//! $ cargo run --example EXNAME --all-features
//! ```
//!
//! which, for example, adds access to a system [::log].
//!
//! All the same can be accessed, for example, by using [aiocoap-client]:
//!
//! ```sh
//! $ aiocoap-client coap://localhost/.well-known/core
//! # application/link-format content was re-formatted
//! <>; ct=0; title="Landing page",
//! </time>; ct=0; title=Clock,
//! </poem>; sz=1338,
//! </cbor/1>; ct=60,
//! </cbor/2>; ct=60,
//! </cbor>; ct=60,
//! </message/warn>; title="POST warning texts here",
//! </message/info>; title="POST info texts here",
//! </log>; title="Most recent log messages"; if="tag:riot-os.org,2021:ser-out"
//!
//! $ aiocoap-client coap://localhost/cbor
//! # CBOR message shown in Diagnostic Notation
//! {0: false, 1: 32, 2: "Hello", 3: [1, 2, 3]}
//! ```
//!
//! The `/log` resource is rather hard to use manually, but there is a [tool for
//! it](https://pypi.org/project/coap-console/):
//!
//! ```sh
//! $ pipx run coap-console coap://localhost
//! INFO Server is ready.
//! ```
//!
//! This produces a continuous output of log activity as it happens; you can add entries from
//! another terminal using:
//!
//! ```sh
//! $ aiocoap-client coap://localhost/message/info -m POST --payload "This will be shown in a moment"
//! ```
//!
//! [coap-message]: https://crates.io/crates/coap-message
//! [aiocoap-client]: https://aiocoap.readthedocs.io/en/latest/installation.html
#![no_std]

#[cfg(feature = "std")]
extern crate std;

#[cfg(feature = "alloc")]
extern crate alloc;

pub mod cbor;

pub mod helloworld;

#[cfg(feature = "with-log")]
pub mod log;
#[cfg(not(feature = "with-log"))]
pub mod log {
    /// A never-ish type that allows having a single full_application_tree function independent of
    /// whether things are built with-log or without.
    pub enum Log {}
}

/// Build a handler that contains all the demo applications
///
/// Note that no log::Log is created because that needs to be global, created-only-once and
/// available as early in the application start-up as possible as per the log crate's design; you'd
/// have to pass one in if you want to view the log through CoAP.
///
/// The resources /message/info and /message/warn for creating log messages will
/// be available independently thereof as long as the with-log feature is active.
pub fn full_application_tree(
    main_log: Option<&'static crate::log::Log>,
) -> impl coap_handler::Handler + coap_handler::Reporting {
    #[cfg(feature = "with-log")]
    use crate::log::*;
    use cbor::*;
    use helloworld::*;

    #[cfg(feature = "std")]
    let shared_cbor: std::sync::Arc<std::sync::Mutex<MyCBOR>> = Default::default();

    use coap_handler_implementations::{
        HandlerBuilder, ReportingHandlerBuilder, TypeHandler, new_dispatcher, with_get_put_fetch,
    };

    use coap_handler::Attribute::*;

    let handler = new_dispatcher()
        .at_with_attributes(&[], &[Ct(0), Title("Landing page")], WELCOME)
        .at_with_attributes(&["time"], &[Ct(0), Title("Clock")], TIME)
        .at_with_attributes(&["poem"], &[Sz(POEM_TEXT_LEN)], POEM);
    #[cfg(feature = "std")]
    let handler = handler
        .at_with_attributes(
            &["cbor", "1"],
            &[Ct(60)],
            TypeHandler::new_minicbor_2(with_get_put_fetch(TryingThroughMutex(
                shared_cbor.clone(),
            ))),
        )
        .at_with_attributes(
            &["cbor", "2"],
            &[Ct(60)],
            TypeHandler::new_minicbor_2(with_get_put_fetch(TryingThroughMutex(shared_cbor))),
        );
    let handler = handler.at_with_attributes(
        &["cbor"],
        &[Ct(60)],
        TypeHandler::new_minicbor_2(with_get_put_fetch(MyCBOR::default())),
    );

    #[cfg(feature = "with-log")]
    let handler = handler
        .at_with_attributes(
            &["message", "warn"],
            &[Title("POST warning texts here")],
            LogMessagePostHandler::new_warn(),
        )
        .at_with_attributes(
            &["message", "info"],
            &[Title("POST info texts here")],
            LogMessagePostHandler::new_info(),
        )
        .at_with_attributes(
            &["log"],
            // Adding `.at()` would work too, but we wouldn't get to set a title.
            &[
                Title("Most recent log messages"),
                Interface("tag:riot-os.org,2021:ser-out"),
            ],
            main_log.map(|l| l.handler()),
        );
    #[cfg(not(feature = "with-log"))]
    let _ = main_log; // Discarding what's sure to be None (because Ok can never be)

    handler.with_wkc()
}