riot_coap_handler_demos/
led.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! Handler that expose a single or all LEDs of a board
//
// TBD: Generalize like GPIOs/I2C to be independent of RIOT

use coap_handler_implementations::{
    new_dispatcher, wkc, Empty, HandlerBuilder, TypeHandler, TypeRenderable,
};
use riot_wrappers::led::{LedNotPresent, LED};

use switch_hal::OutputSwitch;
use switch_hal::ToggleableOutputSwitch;

struct OneLED<const I: u8>(LED<I>);

// FIXME: It doesn't really make sense to have this monomorphized over I, especially as most of the
// time one would have multiple such handlers. This should be trivial to change if riot-wrappers
// LED gains a const-erased version.

impl<const I: u8> TypeRenderable for OneLED<I> {
    type Get = ();
    type Put = bool;
    type Post = Empty;

    fn put(&mut self, data: &bool) -> u8 {
        if *data {
            self.0.on().expect("LEDs are infallible");
        } else {
            self.0.off().expect("LEDs are infallible");
        }
        coap_numbers::code::CHANGED
    }

    fn post(&mut self, _data: &Empty) -> u8 {
        self.0.toggle().expect("LEDs are infallible");
        coap_numbers::code::CHANGED
    }
}

/// Handler for a single LED.
///
/// The handler accepts PUTs of CBOR booleans true or false. It also accepts POSTs, which for
/// simplicity of the implementation right now require a CBOR `null` value in the payload.
pub fn single_led<const I: u8>(
    led: LED<I>,
) -> impl coap_handler::Handler + coap_handler::Reporting {
    let handler = TypeHandler::new_minicbor_0_24(OneLED(led));

    wkc::ConstantSingleRecordReport::new(
        handler,
        &[coap_handler::Attribute::ResourceType(
            "tag:chrysn@fsfe.org,2024-08-24:led",
        )],
    )
}

/// Handler for a single LED, which is only shown if the LED is present.
///
/// See [`single_led()`] for how it is accessed.
pub fn single_led_maybe<const I: u8>(
    led: Result<LED<I>, LedNotPresent>,
) -> impl coap_handler::Handler + coap_handler::Reporting {
    // Option<Handler> implements Handler and 4.04s on None, and does suppresses the report.
    led.ok().map(|led| {
        wkc::ConstantSingleRecordReport::new(
            TypeHandler::new_minicbor(OneLED(led)),
            &[coap_handler::Attribute::ResourceType(
                "tag:chrysn@fsfe.org,2024-08-24:led",
            )],
        )
    })
}

/// A handler that contains sub-resources for all LEDs present on a system.
///
/// It has sub-resources `0` up to `7` (depending on which LEDs are present on the board).
///
/// Use this with `.below()`; if no extra Uri-Path option is present (eg. when placed
/// `.at(&["leds"], ...)` requesting `coap://.../leds`), it does not even implement a resource
/// there.
///
/// This is subtly different from the [`crate::saul`] module where SAUL reports a single resource
/// in .well-known/core. There are no hard rules to follow in which to pick, but generally there
/// are few LEDs and there can be many SAUL resources.
pub fn all_leds() -> impl coap_handler::Handler + coap_handler::Reporting {
    new_dispatcher()
        .at(&["0"], single_led_maybe(LED::<0>::new_checked()))
        .at(&["1"], single_led_maybe(LED::<1>::new_checked()))
        .at(&["2"], single_led_maybe(LED::<2>::new_checked()))
        .at(&["3"], single_led_maybe(LED::<3>::new_checked()))
        .at(&["4"], single_led_maybe(LED::<4>::new_checked()))
        .at(&["5"], single_led_maybe(LED::<5>::new_checked()))
        .at(&["6"], single_led_maybe(LED::<6>::new_checked()))
        .at(&["7"], single_led_maybe(LED::<7>::new_checked()))
}