1use core::marker::PhantomData;
2
3use embedded_interfaces::{TransportError, commands::Command, define_executor};
4use heapless::Vec;
5
6#[cfg_attr(feature = "defmt", derive(defmt::Format))]
8#[derive(Debug, thiserror::Error)]
9pub enum Crc8Error {
10 #[error("the calculated crc checksum {calculated:#02x} did not match the expected value {expected:#02x}")]
11 CrcMismatch { calculated: u8, expected: u8 },
12}
13
14pub trait SensirionCommand {
16 const COMMAND_ID_LEN: usize;
18 const COMMAND_ID: u64;
20 const EXECUTION_TIME_MS: u32;
23}
24
25pub trait SensirionSendCommand:
28 SensirionCommand + Command<I2cExecutor = SensirionSendCommandExecutor<Self>, In = (), Out = ()>
29{
30}
31
32pub trait SensirionWriteCommand:
35 SensirionCommand + Command<I2cExecutor = SensirionWriteCommandExecutor<Self>, Out = ()>
36{
37}
38
39pub trait SensirionReadCommand:
42 SensirionCommand + Command<I2cExecutor = SensirionReadCommandExecutor<Self>, In = ()>
43{
44}
45
46pub trait SensirionWriteReadCommand:
50 SensirionCommand + Command<I2cExecutor = SensirionWriteReadCommandExecutor<Self>>
51{
52}
53
54define_executor!(SensirionSendCommandExecutor, SensirionSendCommand, Crc8Error);
58define_executor!(SensirionWriteCommandExecutor, SensirionWriteCommand, Crc8Error);
59define_executor!(SensirionReadCommandExecutor, SensirionReadCommand, Crc8Error);
60define_executor!(SensirionWriteReadCommandExecutor, SensirionWriteReadCommand, Crc8Error);
61
62#[maybe_async_cfg::maybe(
63 idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Executor, I2cBoundBus),
64 sync(feature = "sync"),
65 async(feature = "async"),
66 keep_self
67)]
68impl<C: SensirionSendCommand> embedded_interfaces::commands::i2c::Executor for SensirionSendCommandExecutor<C> {
69 async fn execute<D, I, A>(
70 delay: &mut D,
71 bound_bus: &mut embedded_interfaces::i2c::I2cBoundBus<I, A>,
72 _input: C::In,
73 ) -> Result<C::Out, TransportError<Self::Error, I::Error>>
74 where
75 D: hal::delay::DelayNs,
76 I: hal::i2c::I2c<A> + hal::i2c::ErrorType,
77 A: hal::i2c::AddressMode + Copy + core::fmt::Debug,
78 {
79 #[cfg(feature = "trace-communication")]
80 log::trace!(
81 "I2C [32mwrite[m i2c_addr={:?} SensirionSendCommand(id={id:0w$x})",
82 bound_bus.address,
83 id = C::COMMAND_ID,
84 w = C::COMMAND_ID_LEN
85 );
86
87 let header = &C::COMMAND_ID.to_be_bytes()[core::mem::size_of_val(&C::COMMAND_ID) - C::COMMAND_ID_LEN..];
88 bound_bus.interface.write(bound_bus.address, header).await?;
89 delay.delay_ms(C::EXECUTION_TIME_MS).await;
90 Ok(())
91 }
92}
93
94#[maybe_async_cfg::maybe(
95 idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Executor, I2cBoundBus),
96 sync(feature = "sync"),
97 async(feature = "async"),
98 keep_self
99)]
100impl<C: SensirionWriteCommand> embedded_interfaces::commands::i2c::Executor for SensirionWriteCommandExecutor<C> {
101 async fn execute<D, I, A>(
102 delay: &mut D,
103 bound_bus: &mut embedded_interfaces::i2c::I2cBoundBus<I, A>,
104 input: C::In,
105 ) -> Result<C::Out, TransportError<Self::Error, I::Error>>
106 where
107 D: hal::delay::DelayNs,
108 I: hal::i2c::I2c<A> + hal::i2c::ErrorType,
109 A: hal::i2c::AddressMode + Copy + core::fmt::Debug,
110 {
111 const CHUNK_SIZE: usize = 2;
112
113 #[cfg(feature = "trace-communication")]
114 log::trace!(
115 "I2C [32mwrite[m i2c_addr={:?} SensirionWriteCommand(id={id:0w$x}):\n{}",
116 bound_bus.address,
117 embedded_interfaces::BitdumpFormattable::bitdump(&input),
118 id = C::COMMAND_ID,
119 w = C::COMMAND_ID_LEN,
120 );
121
122 let header = &C::COMMAND_ID.to_be_bytes()[core::mem::size_of_val(&C::COMMAND_ID) - C::COMMAND_ID_LEN..];
123 let mut array = Vec::<u8, 64>::new();
124 array
125 .extend_from_slice(header)
126 .map_err(|_| TransportError::Unexpected("command id: too large for command buffer"))?;
127
128 let crc = crc::Crc::<u8>::new(&crc::CRC_8_NRSC_5);
129 let data = bytemuck::bytes_of(&input);
130 for x in data.chunks(CHUNK_SIZE) {
131 array
132 .extend_from_slice(x)
133 .map_err(|_| TransportError::Unexpected("command data: too large for buffer"))?;
134 array
135 .push(crc.checksum(x))
136 .map_err(|_| TransportError::Unexpected("command data: too large for buffer"))?;
137 }
138
139 bound_bus.interface.write(bound_bus.address, &array).await?;
140 delay.delay_ms(C::EXECUTION_TIME_MS).await;
141 Ok(())
142 }
143}
144
145#[maybe_async_cfg::maybe(
146 idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Executor, I2cBoundBus),
147 sync(feature = "sync"),
148 async(feature = "async"),
149 keep_self
150)]
151impl<C: SensirionReadCommand> embedded_interfaces::commands::i2c::Executor for SensirionReadCommandExecutor<C> {
152 async fn execute<D, I, A>(
153 delay: &mut D,
154 bound_bus: &mut embedded_interfaces::i2c::I2cBoundBus<I, A>,
155 _input: C::In,
156 ) -> Result<C::Out, TransportError<Self::Error, I::Error>>
157 where
158 D: hal::delay::DelayNs,
159 I: hal::i2c::I2c<A> + hal::i2c::ErrorType,
160 A: hal::i2c::AddressMode + Copy + core::fmt::Debug,
161 {
162 use bytemuck::Zeroable;
163 const CHUNK_SIZE: usize = 2;
164
165 let header = &C::COMMAND_ID.to_be_bytes()[core::mem::size_of_val(&C::COMMAND_ID) - C::COMMAND_ID_LEN..];
166 bound_bus.interface.write(bound_bus.address, header).await?;
167 delay.delay_ms(C::EXECUTION_TIME_MS).await;
168
169 let mut array = Vec::<u8, 64>::new();
170 let mut out = C::Out::zeroed();
171 let data = bytemuck::bytes_of_mut(&mut out);
172 array
173 .resize_default(data.len() + data.len() / CHUNK_SIZE)
174 .map_err(|_| TransportError::Unexpected("command response data: too large for buffer"))?;
175
176 bound_bus.interface.read(bound_bus.address, &mut array).await?;
177
178 let crc = crc::Crc::<u8>::new(&crc::CRC_8_NRSC_5);
179 for (i, x) in array.chunks(CHUNK_SIZE + 1).enumerate() {
180 let value = &x[0..CHUNK_SIZE];
181 data[(i * CHUNK_SIZE)..((i + 1) * CHUNK_SIZE)].copy_from_slice(value);
182
183 let calculated = crc.checksum(value);
184 let expected = x[CHUNK_SIZE];
185 if expected != calculated {
186 return Err(TransportError::Codec(Crc8Error::CrcMismatch { calculated, expected }));
187 }
188 }
189
190 #[cfg(feature = "trace-communication")]
191 log::trace!(
192 "I2C [31mread[m i2c_addr={:?} SensirionReadCommand(id={id:0w$x}):\n{}",
193 bound_bus.address,
194 embedded_interfaces::BitdumpFormattable::bitdump(&out),
195 id = C::COMMAND_ID,
196 w = C::COMMAND_ID_LEN,
197 );
198
199 Ok(out)
200 }
201}
202
203#[maybe_async_cfg::maybe(
204 idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), Executor, I2cBoundBus),
205 sync(feature = "sync"),
206 async(feature = "async"),
207 keep_self
208)]
209impl<C: SensirionWriteReadCommand> embedded_interfaces::commands::i2c::Executor
210 for SensirionWriteReadCommandExecutor<C>
211{
212 async fn execute<D, I, A>(
213 delay: &mut D,
214 bound_bus: &mut embedded_interfaces::i2c::I2cBoundBus<I, A>,
215 input: C::In,
216 ) -> Result<C::Out, TransportError<Self::Error, I::Error>>
217 where
218 D: hal::delay::DelayNs,
219 I: hal::i2c::I2c<A> + hal::i2c::ErrorType,
220 A: hal::i2c::AddressMode + Copy + core::fmt::Debug,
221 {
222 use bytemuck::Zeroable;
223 const CHUNK_SIZE: usize = 2;
224
225 #[cfg(feature = "trace-communication")]
226 log::trace!(
227 "I2C [32mwrite[m i2c_addr={:?} SensirionWriteReadCommand(id={id:0w$x}):\n{}",
228 bound_bus.address,
229 embedded_interfaces::BitdumpFormattable::bitdump(&input),
230 id = C::COMMAND_ID,
231 w = C::COMMAND_ID_LEN,
232 );
233
234 let header = &C::COMMAND_ID.to_be_bytes()[core::mem::size_of_val(&C::COMMAND_ID) - C::COMMAND_ID_LEN..];
235 let mut array = Vec::<u8, 64>::new();
236 array
237 .extend_from_slice(header)
238 .map_err(|_| TransportError::Unexpected("command id: too large for command buffer"))?;
239
240 let crc = crc::Crc::<u8>::new(&crc::CRC_8_NRSC_5);
241 let data = bytemuck::bytes_of(&input);
242 for x in data.chunks(CHUNK_SIZE) {
243 array
244 .extend_from_slice(x)
245 .map_err(|_| TransportError::Unexpected("command data: too large for buffer"))?;
246 array
247 .push(crc.checksum(x))
248 .map_err(|_| TransportError::Unexpected("command data: too large for buffer"))?;
249 }
250
251 bound_bus.interface.write(bound_bus.address, &array).await?;
252 delay.delay_ms(C::EXECUTION_TIME_MS).await;
253
254 let mut array = Vec::<u8, 64>::new();
255 let mut out = C::Out::zeroed();
256 let data = bytemuck::bytes_of_mut(&mut out);
257 array
258 .resize_default(data.len() + data.len() / CHUNK_SIZE)
259 .map_err(|_| TransportError::Unexpected("command response data: too large for buffer"))?;
260
261 bound_bus.interface.read(bound_bus.address, &mut array).await?;
262
263 let crc = crc::Crc::<u8>::new(&crc::CRC_8_NRSC_5);
264 for (i, x) in array.chunks(CHUNK_SIZE + 1).enumerate() {
265 let value = &x[0..CHUNK_SIZE];
266 data[(i * CHUNK_SIZE)..((i + 1) * CHUNK_SIZE)].copy_from_slice(value);
267
268 let calculated = crc.checksum(value);
269 let expected = x[CHUNK_SIZE];
270 if expected != calculated {
271 return Err(TransportError::Codec(Crc8Error::CrcMismatch { calculated, expected }));
272 }
273 }
274
275 #[cfg(feature = "trace-communication")]
276 log::trace!(
277 "I2C [31mread[m i2c_addr={:?} SensirionWriteReadCommand(id={id:0w$x}):\n{}",
278 bound_bus.address,
279 embedded_interfaces::BitdumpFormattable::bitdump(&out),
280 id = C::COMMAND_ID,
281 w = C::COMMAND_ID_LEN,
282 );
283
284 Ok(out)
285 }
286}
287
288#[allow(unused)]
289macro_rules! define_send_command {
290 ($name:ident, id_len=$id_len:literal, id=$id:expr, time=$time_ms:expr) => {
291 impl embedded_interfaces::commands::Command for $name {
292 type In = ();
293 type Out = ();
294 type ExecutorError = crate::devices::sensirion::commands::Crc8Error;
295 type SpiExecutor = embedded_interfaces::commands::unsupported_executor::UnsupportedExecutor<
296 crate::devices::sensirion::commands::Crc8Error,
297 Self,
298 >;
299 type I2cExecutor = crate::devices::sensirion::commands::SensirionSendCommandExecutor<Self>;
300 }
301 impl crate::devices::sensirion::commands::SensirionCommand for $name {
302 const COMMAND_ID_LEN: usize = $id_len;
303 const COMMAND_ID: u64 = $id;
304 const EXECUTION_TIME_MS: u32 = $time_ms;
305 }
306 impl crate::devices::sensirion::commands::SensirionSendCommand for $name {}
307 };
308}
309#[allow(unused)]
310pub(crate) use define_send_command;
311
312#[allow(unused)]
313macro_rules! define_write_command {
314 ($name:ident, id_len=$id_len:literal, id=$id:expr, time=$time_ms:expr, in=$in:ty) => {
315 impl embedded_interfaces::commands::Command for $name {
316 type In = $in;
317 type Out = ();
318 type ExecutorError = crate::devices::sensirion::commands::Crc8Error;
319 type SpiExecutor = embedded_interfaces::commands::unsupported_executor::UnsupportedExecutor<
320 crate::devices::sensirion::commands::Crc8Error,
321 Self,
322 >;
323 type I2cExecutor = crate::devices::sensirion::commands::SensirionWriteCommandExecutor<Self>;
324 }
325 impl crate::devices::sensirion::commands::SensirionCommand for $name {
326 const COMMAND_ID_LEN: usize = $id_len;
327 const COMMAND_ID: u64 = $id;
328 const EXECUTION_TIME_MS: u32 = $time_ms;
329 }
330 impl crate::devices::sensirion::commands::SensirionWriteCommand for $name {}
331 };
332}
333#[allow(unused)]
334pub(crate) use define_write_command;
335
336#[allow(unused)]
337macro_rules! define_read_command {
338 ($name:ident, id_len=$id_len:literal, id=$id:expr, time=$time_ms:expr, out=$out:ty) => {
339 impl embedded_interfaces::commands::Command for $name {
340 type In = ();
341 type Out = $out;
342 type ExecutorError = crate::devices::sensirion::commands::Crc8Error;
343 type SpiExecutor = embedded_interfaces::commands::unsupported_executor::UnsupportedExecutor<
344 crate::devices::sensirion::commands::Crc8Error,
345 Self,
346 >;
347 type I2cExecutor = crate::devices::sensirion::commands::SensirionReadCommandExecutor<Self>;
348 }
349 impl crate::devices::sensirion::commands::SensirionCommand for $name {
350 const COMMAND_ID_LEN: usize = $id_len;
351 const COMMAND_ID: u64 = $id;
352 const EXECUTION_TIME_MS: u32 = $time_ms;
353 }
354 impl crate::devices::sensirion::commands::SensirionReadCommand for $name {}
355 };
356}
357#[allow(unused)]
358pub(crate) use define_read_command;
359
360#[allow(unused)]
361macro_rules! define_write_read_command {
362 ($name:ident, id_len=$id_len:literal, id=$id:expr, time=$time_ms:expr, in=$in:ty, out=$out:ty) => {
363 impl embedded_interfaces::commands::Command for $name {
364 type In = $in;
365 type Out = $out;
366 type ExecutorError = crate::devices::sensirion::commands::Crc8Error;
367 type SpiExecutor = embedded_interfaces::commands::unsupported_executor::UnsupportedExecutor<
368 crate::devices::sensirion::commands::Crc8Error,
369 Self,
370 >;
371 type I2cExecutor = crate::devices::sensirion::commands::SensirionWriteReadCommandExecutor<Self>;
372 }
373 impl crate::devices::sensirion::commands::SensirionCommand for $name {
374 const COMMAND_ID_LEN: usize = $id_len;
375 const COMMAND_ID: u64 = $id;
376 const EXECUTION_TIME_MS: u32 = $time_ms;
377 }
378 impl crate::devices::sensirion::commands::SensirionWriteReadCommand for $name {}
379 };
380}
381#[allow(unused)]
382pub(crate) use define_write_read_command;
383
384#[allow(unused)]
385macro_rules! define_sensirion_commands {
386 (
388 id_len $id_len:literal;
389 marker [
390 $(($feature:literal, $trait_path:path)),* $(,)?
391 ];
392 $($rest:tt)*
393 ) => {
394 define_sensirion_commands! {
395 @id_len $id_len;
396 @markers [$(($feature, $trait_path)),*];
397 @parse [$($rest)*]
398 }
399 };
400
401 (
403 @id_len $id_len:literal;
404 @markers [$($markers:tt)*];
405 @parse [
406 $(#[doc = $doc:literal])*
407 send $id:literal time_ms=$time:literal $name:ident();
408 $($rest:tt)*
409 ]
410 ) => {
411 define_sensirion_commands! { @emit_send { [$( #[doc = $doc] )*], $name, $id_len, $id, $time } }
412 define_sensirion_commands! { @emit_impls { $name, [$($markers)*] } }
413 define_sensirion_commands! {
414 @id_len $id_len;
415 @markers [$($markers)*];
416 @parse [$($rest)*]
417 }
418 };
419
420 (
421 @id_len $id_len:literal;
422 @markers [$($markers:tt)*];
423 @parse [
424 $(#[doc = $doc:literal])*
425 read $id:literal time_ms=$time:literal $name:ident() -> $out:ty;
426 $($rest:tt)*
427 ]
428 ) => {
429 define_sensirion_commands! { @emit_read { [$( #[doc = $doc] )*], $name, $id_len, $id, $time, $out } }
430 define_sensirion_commands! { @emit_impls { $name, [$($markers)*] } }
431 define_sensirion_commands! {
432 @id_len $id_len;
433 @markers [$($markers)*];
434 @parse [$($rest)*]
435 }
436 };
437
438 (
439 @id_len $id_len:literal;
440 @markers [$($markers:tt)*];
441 @parse [
442 $(#[doc = $doc:literal])*
443 write $id:literal time_ms=$time:literal $name:ident($in:ty);
444 $($rest:tt)*
445 ]
446 ) => {
447 define_sensirion_commands! { @emit_write { [$( #[doc = $doc] )*], $name, $id_len, $id, $time, $in } }
448 define_sensirion_commands! { @emit_impls { $name, [$($markers)*] } }
449 define_sensirion_commands! {
450 @id_len $id_len;
451 @markers [$($markers)*];
452 @parse [$($rest)*]
453 }
454 };
455
456 (
457 @id_len $id_len:literal;
458 @markers [$($markers:tt)*];
459 @parse [
460 $(#[doc = $doc:literal])*
461 write_read $id:literal time_ms=$time:literal $name:ident($in:ty) -> $out:ty;
462 $($rest:tt)*
463 ]
464 ) => {
465 define_sensirion_commands! { @emit_write_read { [$( #[doc = $doc] )*], $name, $id_len, $id, $time, $in, $out } }
466 define_sensirion_commands! { @emit_impls { $name, [$($markers)*] } }
467 define_sensirion_commands! {
468 @id_len $id_len;
469 @markers [$($markers)*];
470 @parse [$($rest)*]
471 }
472 };
473
474 (
476 @id_len $id_len:literal;
477 @markers [$($markers:tt)*];
478 @parse []
479 ) => {};
480
481 (@emit_send { [$( $doc:tt )*], $name:ident, $id_len:literal, $id:literal, $time:literal }) => {
483 $( $doc )*
484 #[doc = ""]
485 #[doc = concat!("Execution time (ms): `", stringify!($time), "`")]
486 pub struct $name {}
487
488 crate::devices::sensirion::commands::define_send_command!($name, id_len=$id_len, id=$id, time=$time);
489 };
490
491 (@emit_read { [$( $doc:tt )*], $name:ident, $id_len:literal, $id:literal, $time:literal, $out:ty }) => {
492 $( $doc )*
493 #[doc = ""]
494 #[doc = concat!("Out: [`", stringify!($out), "`]")]
495 #[doc = ""]
496 #[doc = concat!("Execution time (ms): `", stringify!($time), "`")]
497 pub struct $name {}
498
499 crate::devices::sensirion::commands::define_read_command!($name, id_len=$id_len, id=$id, time=$time, out=$out);
500 };
501
502 (@emit_write { [$( $doc:tt )*], $name:ident, $id_len:literal, $id:literal, $time:literal, $in:ty }) => {
503 $( $doc )*
504 #[doc = ""]
505 #[doc = concat!("In: [`", stringify!($in), "`]")]
506 #[doc = ""]
507 #[doc = concat!("Execution time (ms): `", stringify!($time), "`")]
508 pub struct $name {}
509
510 crate::devices::sensirion::commands::define_write_command!($name, id_len=$id_len, id=$id, time=$time, in=$in);
511 };
512
513 (@emit_write_read { [$( $doc:tt )*], $name:ident, $id_len:literal, $id:literal, $time:literal, $in:ty, $out:ty }) => {
514 $( $doc )*
515 #[doc = ""]
516 #[doc = concat!("In: [`", stringify!($in), "`]")]
517 #[doc = ""]
518 #[doc = concat!("Out: [`", stringify!($out), "`]")]
519 #[doc = ""]
520 #[doc = concat!("Execution time (ms): `", stringify!($time), "`")]
521 pub struct $name {}
522
523 crate::devices::sensirion::commands::define_write_read_command!($name, id_len=$id_len, id=$id, time=$time, in=$in, out=$out);
524 };
525
526 (
528 @emit_impls { $name:ident, [$(($feature:literal, $trait_path:path)),*] }
529 ) => {
530 $(
531 #[cfg(feature = $feature)]
532 impl $trait_path for $name {}
533 )*
534 };
535}
536#[allow(unused)]
537pub(crate) use define_sensirion_commands;