riot_coap_handler_demos/i2c.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
//! 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::i2c::I2c::write_read]. 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 coap_message::{
Code, MinimalWritableMessage, MutableWritableMessage, OptionNumber, ReadableMessage,
};
use embedded_hal::i2c::I2c;
use serde::Deserialize;
use crate::common::ErrorDetail;
struct I2Cbor<I: I2c>(I);
#[derive(Deserialize)]
struct Request<'a> {
address: u8,
write: &'a [u8],
read_length: usize,
}
struct ReadFrom {
address: u8,
length: usize,
}
impl<I: I2c> 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: I2c>(i2c: I) -> impl coap_handler::Handler {
I2Cbor(i2c)
}