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
//! An I2C device wrapped in a CoAP resource
//!
//! This expreses the bus as a CBOR resource to which a [int, bytes, int] object is posted
//! indicating the address, any to-be-written bytes and the number of bytes to be read, returning
//! an application/octet-stream representation of the read data.
//!
//! Writes and reads are performed in separate steps; this allows the CoAP library to swap out the
//! message buffers inbetween. Thus, if the bus is multi-master or shared in the application, there
//! may be cross-talk (eg. by another master selecting something different for reading in the
//! meantime). Two mitigations are possible, neither currently implemented:
//!
//! * Use [embedded_hal::blocking::i2c::WriteRead]. This requires a buffer to be kept across the
//!   request and response processing.
//!
//! * Add a bus locking feature to embedded_hal (similar to RIOT's bus acquisition, but not only
//!   against other local applications performing operations, but also against other bus masters
//!   taking the bus inbetween).
//!
//! Note that if this handler is not run at a message deduplicating CoAP handler, bus reads and
//! writes may happen multiple times.
//!
//! TBD:
//!
//! * There is nothing RIOT specific about this; maybe there should be embedded-hal-coap-demos?
//! * There is no good error reporting yet; this should be just enough error reporting to implement
//!   an I2C scanner, but only because an empty-read-empty-write is successful if there's a SACK on
//!   the address.

use embedded_hal::blocking::i2c::{Write, Read};
use coap_message::{Code, OptionNumber, ReadableMessage, MinimalWritableMessage, MutableWritableMessage, error::RenderableOnMinimal};
use serde::Deserialize;

use crate::common::ErrorDetail;

struct I2Cbor<I: Write + Read>(I);

#[derive(Deserialize)]
struct Request<'a> {
    address: u8,
    write: &'a [u8],
    read_length: usize,
}

struct ReadFrom {
    address: u8,
    length: usize,
}

impl<I: Write + Read> coap_handler::Handler for I2Cbor<I> {
    type RequestData = ReadFrom;

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

    fn extract_request_data<M: ReadableMessage>(&mut self, request: &M) -> Result<ReadFrom, ErrorDetail> {
        // FIXME check code; error handling
        let request: Request = serde_cbor::de::from_slice_with_scratch(
            request.payload(),
            &mut [],
            )
            .map_err(|_| ErrorDetail("Failed to parse: use CBOR [addr, b'write', read-length]"))?;
        if request.write.len() > 0 {
            self.0.write(request.address, request.write)
                .map_err(|_| ErrorDetail("Write failed"))?;
        }
        Ok(ReadFrom { address: request.address, length: request.read_length })
    }
    fn estimate_length(&mut self, request: &Self::RequestData) -> usize {
        match request {
            // not precise, but sufficient for CBOR encoding and content format
            ReadFrom { length, .. } => length + 16,
        }
    }

    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)?);

        let ReadFrom { address, length } = request;
        response.add_option_uint(OptionNumber::new(coap_numbers::option::CONTENT_FORMAT)?,
                                    42u8 /* application/octet-stream */
                                    )?;
        let buffer = response.payload_mut_with_len(length)?;
        if length > 0 {
            if let Err(_) = self.0.read(address, buffer) {
                todo!("Read failed -- and error handlign was simplified a bit too much");
                // request = Err("Read failed");
            }
        }
        Ok(())
    }
}

pub fn handler_for<I: Write + Read>(i2c: I) -> impl coap_handler::Handler {
    I2Cbor(i2c)
}