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
//! 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 embedded_hal::digital::v2::{InputPin, OutputPin};
use coap_message::{Code, ReadableMessage, MinimalWritableMessage, MutableWritableMessage, OptionNumber};

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 {
    InputWrapper(pin)
}

pub fn handler_for_output<P: OutputPin>(pin: P) -> impl coap_handler::Handler {
    OutputWrapper(pin)
}