riot_coap_handler_demos/
gpio.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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! A GPIO pin wrapped in a CoAP resource
//!
//! This expresses the pins as a CBOR resource that's true for high and false for low.
//!
//! * There is nothing RIOT specific about this; maybe there should be embedded-hal-coap-demos?
//!
//!   Probably not, as while *now* this only maps in and out pins, a future version could allow
//!   putting a configuration there as well, and that exceeds what embedded-hal can do.

use coap_message::{
    Code, MinimalWritableMessage, MutableWritableMessage, OptionNumber, ReadableMessage,
};
use embedded_hal::digital::{InputPin, OutputPin};

use crate::common::ErrorDetail;

struct OutputWrapper<P: OutputPin>(P);
struct InputWrapper<P: InputPin>(P);

impl<P: InputPin> coap_handler::Handler for InputWrapper<P> {
    type RequestData = ();

    type ExtractRequestError = core::convert::Infallible;
    type BuildResponseError<M: MinimalWritableMessage> = M::UnionError;

    fn extract_request_data<M: ReadableMessage>(
        &mut self,
        _request: &M,
    ) -> Result<Self::RequestData, Self::ExtractRequestError> {
        // FIXME check code; error handling
        Ok(())
    }
    fn estimate_length(&mut self, _request: &Self::RequestData) -> usize {
        3
    }

    fn build_response<M: MutableWritableMessage>(
        &mut self,
        response: &mut M,
        _request: Self::RequestData,
    ) -> Result<(), M::UnionError> {
        if let Ok(high) = self.0.is_high() {
            response.set_code(Code::new(coap_numbers::code::CONTENT)?);
            response.add_option_uint(
                OptionNumber::new(coap_numbers::option::CONTENT_FORMAT)?,
                60u8, /* application/cbor */
            )?;
            response.set_payload(if high {
                b"\xf5" /* true */
            } else {
                b"\xf4" /* false */
            })?;
        } else {
            response.set_code(Code::new(coap_numbers::code::INTERNAL_SERVER_ERROR)?);
            response.set_payload(b"GPIO read error")?;
        }
        Ok(())
    }
}

impl<P: OutputPin> coap_handler::Handler for OutputWrapper<P> {
    type RequestData = ();

    type ExtractRequestError = ErrorDetail;
    type BuildResponseError<M: MinimalWritableMessage> = M::UnionError;

    fn extract_request_data<M: ReadableMessage>(&mut self, request: &M) -> Result<(), ErrorDetail> {
        // FIXME check code; error handling
        match request.payload() {
            b"\xf5" /* true */ => self.0.set_high().map_err(|_| ErrorDetail("Failure setting pin")),
            b"\xf4" /* false */=> self.0.set_low().map_err(|_| ErrorDetail("Failure setting pin")),
            _ => Err(ErrorDetail("PUT value must be CBOR true or false")),
        }
    }
    fn estimate_length(&mut self, _request: &Self::RequestData) -> usize {
        4
    }

    fn build_response<M: MutableWritableMessage>(
        &mut self,
        response: &mut M,
        _request: Self::RequestData,
    ) -> Result<(), M::UnionError> {
        response.set_code(Code::new(coap_numbers::code::CHANGED)?);
        Ok(())
    }
}

pub fn handler_for_input<P: InputPin>(
    pin: P,
) -> impl coap_handler::Handler + coap_handler::Reporting {
    coap_handler_implementations::wkc::ConstantSingleRecordReport::new(
        InputWrapper(pin),
        &[coap_handler::Attribute::ResourceType(
            "tag:chrysn@fsfe.org,2024-08-26:gpio",
        )],
    )
}

pub fn handler_for_output<P: OutputPin>(
    pin: P,
) -> impl coap_handler::Handler + coap_handler::Reporting {
    coap_handler_implementations::wkc::ConstantSingleRecordReport::new(
        OutputWrapper(pin),
        &[coap_handler::Attribute::ResourceType(
            "tag:chrysn@fsfe.org,2024-08-26:gpio",
        )],
    )
}