1use std::fmt;
56use std::path::{Path, PathBuf};
57use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
58use std::time::Duration;
59
60use embedded_hal::delay::DelayNs;
61use embedded_hal::i2c::{
62 ErrorKind, ErrorType, I2c, NoAcknowledgeSource, Operation, SevenBitAddress,
63};
64
65use crate::script::{I2cScript, ScriptError};
66use crate::sim::{FromPart, Part, PartError};
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70pub enum BusKind {
71 Adapter,
73 Simulated,
75 Scripted,
77}
78
79#[derive(Clone, Debug, PartialEq, Eq)]
81pub enum OpenError {
82 Unsupported,
85 Adapter {
87 path: PathBuf,
89 reason: String,
91 },
92}
93
94impl fmt::Display for OpenError {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self {
97 OpenError::Unsupported => f.write_str(
98 "an I2C adapter is opened through the kernel's i2c-dev interface, which only Linux has",
99 ),
100 OpenError::Adapter { path, reason } => write!(f, "{}: {reason}", path.display()),
101 }
102 }
103}
104
105impl std::error::Error for OpenError {}
106
107#[derive(Clone, Copy, Debug, PartialEq, Eq)]
109pub struct NotSimulated;
110
111impl fmt::Display for NotSimulated {
112 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113 f.write_str("only a simulated bus takes parts")
114 }
115}
116
117impl std::error::Error for NotSimulated {}
118
119#[derive(Clone, Debug, PartialEq, Eq)]
121pub enum BusError {
122 NoAcknowledge {
124 address: u8,
126 },
127 Part(PartError),
129 Script(ScriptError),
131 Adapter {
133 kind: ErrorKind,
135 reason: String,
137 },
138}
139
140impl fmt::Display for BusError {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 match self {
143 BusError::NoAcknowledge { address } => write!(f, "nothing answered at {address:#04x}"),
144 BusError::Part(error) => write!(f, "{error}"),
145 BusError::Script(error) => write!(f, "{error}"),
146 BusError::Adapter { reason, .. } => f.write_str(reason),
147 }
148 }
149}
150
151impl std::error::Error for BusError {
152 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
153 match self {
154 BusError::Part(error) => Some(error),
155 BusError::Script(error) => Some(error),
156 BusError::NoAcknowledge { .. } | BusError::Adapter { .. } => None,
157 }
158 }
159}
160
161impl embedded_hal::i2c::Error for BusError {
162 fn kind(&self) -> ErrorKind {
163 match self {
164 BusError::NoAcknowledge { .. } => {
165 ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address)
166 }
167 BusError::Part(error) => error.kind(),
168 BusError::Script(error) => error.kind(),
169 BusError::Adapter { kind, .. } => *kind,
170 }
171 }
172}
173
174#[derive(Clone)]
202pub struct I2cBus {
203 shared: Arc<Mutex<Shared>>,
204}
205
206struct Shared {
207 backend: Backend,
208 transfers: usize,
209 waited_ns: u64,
210}
211
212enum Backend {
213 #[cfg(all(feature = "linux", target_os = "linux"))]
214 Adapter {
215 path: PathBuf,
216 device: crate::linux::I2cdev,
217 },
218 Parts(Vec<Part>),
219 Script(I2cScript),
220}
221
222impl Backend {
223 fn kind(&self) -> BusKind {
224 match self {
225 #[cfg(all(feature = "linux", target_os = "linux"))]
226 Backend::Adapter { .. } => BusKind::Adapter,
227 Backend::Parts(_) => BusKind::Simulated,
228 Backend::Script(_) => BusKind::Scripted,
229 }
230 }
231}
232
233fn lock(shared: &Mutex<Shared>) -> MutexGuard<'_, Shared> {
234 shared.lock().unwrap_or_else(PoisonError::into_inner)
235}
236
237fn place(parts: &mut Vec<Part>, part: Part) -> Option<Part> {
239 match parts
240 .iter_mut()
241 .find(|held| held.address() == part.address())
242 {
243 Some(held) => Some(std::mem::replace(held, part)),
244 None => {
245 parts.push(part);
246 None
247 }
248 }
249}
250
251impl I2cBus {
252 fn with(backend: Backend) -> I2cBus {
253 I2cBus {
254 shared: Arc::new(Mutex::new(Shared {
255 backend,
256 transfers: 0,
257 waited_ns: 0,
258 })),
259 }
260 }
261
262 pub fn open(path: impl AsRef<Path>) -> Result<I2cBus, OpenError> {
287 #[cfg(all(feature = "linux", target_os = "linux"))]
288 {
289 let path = path.as_ref().to_path_buf();
290 let device = crate::linux::I2cdev::new(&path).map_err(|error| OpenError::Adapter {
291 path: path.clone(),
292 reason: error.to_string(),
293 })?;
294 Ok(I2cBus::with(Backend::Adapter { path, device }))
295 }
296 #[cfg(not(all(feature = "linux", target_os = "linux")))]
297 {
298 let _ = path;
299 Err(OpenError::Unsupported)
300 }
301 }
302
303 pub fn simulated<P: Into<Part>>(parts: impl IntoIterator<Item = P>) -> I2cBus {
316 let mut placed: Vec<Part> = Vec::new();
317 for part in parts {
318 place(&mut placed, part.into());
319 }
320 I2cBus::with(Backend::Parts(placed))
321 }
322
323 pub fn attach(&self, part: impl Into<Part>) -> Result<Option<Part>, NotSimulated> {
340 match &mut lock(&self.shared).backend {
341 Backend::Parts(parts) => Ok(place(parts, part.into())),
342 _ => Err(NotSimulated),
343 }
344 }
345
346 pub fn scripted(script: I2cScript) -> I2cBus {
357 I2cBus::with(Backend::Script(script))
358 }
359
360 #[must_use]
366 pub fn kind(&self) -> BusKind {
367 lock(&self.shared).backend.kind()
368 }
369
370 #[must_use]
404 pub fn part<P: FromPart>(&self, address: u8) -> Option<P> {
405 match &lock(&self.shared).backend {
406 Backend::Parts(parts) => parts
407 .iter()
408 .find(|part| part.address() == address)
409 .cloned()
410 .and_then(P::from_part),
411 _ => None,
412 }
413 }
414
415 #[must_use]
422 pub fn transfers(&self) -> usize {
423 lock(&self.shared).transfers
424 }
425
426 #[must_use]
433 pub fn remaining(&self) -> Option<usize> {
434 match &lock(&self.shared).backend {
435 Backend::Script(script) => Some(script.remaining()),
436 _ => None,
437 }
438 }
439
440 #[must_use]
446 pub fn waited_micros(&self) -> u64 {
447 lock(&self.shared).waited_ns / 1_000
448 }
449
450 #[must_use]
457 pub fn delay(&self) -> BusDelay {
458 BusDelay {
459 shared: Arc::clone(&self.shared),
460 }
461 }
462}
463
464impl fmt::Debug for I2cBus {
465 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
466 let shared = lock(&self.shared);
467 f.debug_struct("I2cBus")
468 .field("kind", &shared.backend.kind())
469 .field("transfers", &shared.transfers)
470 .finish_non_exhaustive()
471 }
472}
473
474impl ErrorType for I2cBus {
475 type Error = BusError;
476}
477
478impl I2c<SevenBitAddress> for I2cBus {
479 fn transaction(
480 &mut self,
481 address: SevenBitAddress,
482 operations: &mut [Operation<'_>],
483 ) -> Result<(), BusError> {
484 let mut shared = lock(&self.shared);
485 shared.transfers += 1;
486 match &mut shared.backend {
487 #[cfg(all(feature = "linux", target_os = "linux"))]
488 Backend::Adapter { path, device } => {
489 I2c::<SevenBitAddress>::transaction(device, address, operations)
490 .map_err(|error| adapter_error(path, &error))
491 }
492 Backend::Parts(parts) => {
493 let Some(part) = parts.iter_mut().find(|part| part.address() == address) else {
494 return Err(BusError::NoAcknowledge { address });
495 };
496 part.transaction(address, operations)
497 .map_err(BusError::Part)
498 }
499 Backend::Script(script) => script
500 .transaction(address, operations)
501 .map_err(BusError::Script),
502 }
503 }
504}
505
506#[cfg(all(feature = "linux", target_os = "linux"))]
510fn adapter_error(path: &Path, error: &crate::linux::I2CError) -> BusError {
511 use crate::linux::i2cdev::linux::LinuxI2CError;
512 use embedded_hal::i2c::Error as _;
513
514 let errno = match error.inner() {
515 LinuxI2CError::Errno(errno) => Some(*errno),
516 LinuxI2CError::Io(io) => io.raw_os_error(),
517 };
518 let kind = if errno == Some(nix::errno::Errno::EREMOTEIO as i32) {
519 ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown)
520 } else {
521 error.kind()
522 };
523 BusError::Adapter {
524 kind,
525 reason: format!("{}: {error}", path.display()),
526 }
527}
528
529#[derive(Clone)]
531pub struct BusDelay {
532 shared: Arc<Mutex<Shared>>,
533}
534
535impl fmt::Debug for BusDelay {
536 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
537 f.debug_struct("BusDelay").finish_non_exhaustive()
538 }
539}
540
541impl DelayNs for BusDelay {
542 fn delay_ns(&mut self, ns: u32) {
543 let sleeps = {
544 let mut shared = lock(&self.shared);
545 shared.waited_ns = shared.waited_ns.saturating_add(u64::from(ns));
546 shared.backend.kind() == BusKind::Adapter
547 };
548 if sleeps {
549 std::thread::sleep(Duration::from_nanos(u64::from(ns)));
550 }
551 }
552}
553
554#[cfg(test)]
555mod tests {
556 use super::*;
557 use crate::script::I2cStep;
558 use crate::sim::{CommandPart, I2cPart, WordPart};
559 use embedded_hal::i2c::Error as _;
560 use std::time::Instant;
561
562 #[test]
563 fn every_clone_reaches_the_same_parts() {
564 let bus = I2cBus::simulated([I2cPart::new(0x76), I2cPart::new(0x48)]);
565 let mut driver = bus.clone();
566
567 driver.write(0x76, &[0xf4, 0x25]).unwrap();
568 driver.write(0x48, &[0x01, 0x60, 0x20]).unwrap();
569
570 let part = bus.part::<I2cPart>(0x76).map(|part| part.register(0xf4));
571 assert_eq!(part, Some(0x25));
572 let part = bus.part::<I2cPart>(0x48).map(|part| part.register(0x02));
573 assert_eq!(part, Some(0x20));
574 assert_eq!(bus.transfers(), 2);
575 assert_eq!(bus.kind(), BusKind::Simulated);
576 assert_eq!(bus.remaining(), None);
577 }
578
579 #[test]
580 fn a_part_attached_at_a_held_address_takes_its_place() {
581 let bus = I2cBus::simulated([
582 I2cPart::new(0x76).holding(0xd0, &[0x58]),
583 I2cPart::new(0x76).holding(0xd0, &[0x60]),
584 ]);
585 let held = bus.part::<I2cPart>(0x76).map(|part| part.register(0xd0));
586 assert_eq!(held, Some(0x60));
587
588 let replaced = bus
589 .attach(I2cPart::new(0x76).holding(0xd0, &[0x61]))
590 .expect("a simulated bus takes parts");
591 let replaced = replaced
592 .and_then(I2cPart::from_part)
593 .map(|part| part.register(0xd0));
594 assert_eq!(replaced, Some(0x60));
595 let held = bus.part::<I2cPart>(0x76).map(|part| part.register(0xd0));
596 assert_eq!(held, Some(0x61));
597
598 assert!(bus
599 .attach(I2cPart::new(0x48))
600 .expect("a free address")
601 .is_none());
602 assert!(bus.part::<Part>(0x48).is_some());
603 }
604
605 #[test]
606 fn one_bus_holds_parts_of_every_kind() {
607 let mut bus = I2cBus::simulated([
608 Part::from(I2cPart::new(0x76).holding(0xd0, &[0x60])),
609 WordPart::new(0x48).holding(0x0f, 0x0117).into(),
610 CommandPart::new(0x44, 2)
611 .answering(&[0xf3, 0x2d], &[0x80, 0x10, 0xe1])
612 .into(),
613 ]);
614
615 let mut word = [0u8; 2];
616 bus.write_read(0x48, &[0x0f], &mut word).unwrap();
617 assert_eq!(u16::from_be_bytes(word), 0x0117);
618 bus.write(0x44, &[0xf3, 0x2d]).unwrap();
619 let mut status = [0u8; 3];
620 bus.read(0x44, &mut status).unwrap();
621 assert_eq!(status, [0x80, 0x10, 0xe1]);
622
623 assert!(bus.part::<WordPart>(0x48).is_some());
624 assert!(
625 bus.part::<I2cPart>(0x48).is_none(),
626 "a word-wide part is not a byte-wide one"
627 );
628 let commands: CommandPart = bus.part(0x44).expect("a part that takes commands");
629 assert_eq!(commands.received().len(), 1);
630 assert_eq!(
631 bus.read(0x44, &mut status).unwrap_err(),
632 BusError::Part(PartError::NoReply),
633 "the reply was taken"
634 );
635 }
636
637 #[test]
638 fn only_a_simulated_bus_takes_parts() {
639 let bus = I2cBus::scripted(I2cScript::new([]));
640 assert_eq!(bus.attach(I2cPart::new(0x76)).unwrap_err(), NotSimulated);
641 assert_eq!(NotSimulated.to_string(), "only a simulated bus takes parts");
642 }
643
644 #[test]
645 fn an_address_no_part_holds_is_not_acknowledged() {
646 let mut bus = I2cBus::simulated([I2cPart::new(0x76)]);
647
648 let error = bus.write(0x77, &[0xd0]).unwrap_err();
649 assert_eq!(error, BusError::NoAcknowledge { address: 0x77 });
650 assert_eq!(
651 error.kind(),
652 ErrorKind::NoAcknowledge(NoAcknowledgeSource::Address)
653 );
654 assert_eq!(error.to_string(), "nothing answered at 0x77");
655 assert!(bus.part::<Part>(0x77).is_none());
656 assert_eq!(bus.transfers(), 1, "a refused transfer still happened");
657 }
658
659 #[test]
660 fn a_part_refusing_a_read_says_why() {
661 let mut bus = I2cBus::simulated([I2cPart::new(0x76)]);
662 let mut byte = [0u8; 1];
663
664 let error = bus.read(0x76, &mut byte).unwrap_err();
665 assert_eq!(error, BusError::Part(PartError::NoRegister));
666 }
667
668 #[test]
669 fn a_script_counts_what_is_left_and_refuses_the_unexpected() {
670 let mut bus = I2cBus::scripted(I2cScript::new([
671 I2cStep::write(0x76, [0xe0, 0xb6]),
672 I2cStep::write_read(0x76, [0xd0], [0x60]),
673 ]));
674 assert_eq!(bus.remaining(), Some(2));
675
676 bus.write(0x76, &[0xe0, 0xb6]).unwrap();
677 assert_eq!(bus.remaining(), Some(1));
678
679 let mut id = [0u8; 1];
680 let refused = bus.write_read(0x76, &[0xd1], &mut id).unwrap_err();
681 assert!(
682 matches!(
683 refused,
684 BusError::Script(ScriptError::Mismatch { step: 1, .. })
685 ),
686 "{refused:?}"
687 );
688 assert_eq!(
689 bus.remaining(),
690 Some(1),
691 "a refused transfer consumes nothing"
692 );
693
694 bus.write_read(0x76, &[0xd0], &mut id).unwrap();
695 assert_eq!(id, [0x60]);
696 assert_eq!(bus.remaining(), Some(0));
697 assert_eq!(bus.kind(), BusKind::Scripted);
698 assert!(bus.part::<Part>(0x76).is_none());
699 }
700
701 #[test]
702 fn a_simulated_bus_counts_a_wait_without_sleeping_through_it() {
703 let bus = I2cBus::simulated(Vec::<Part>::new());
704 let mut delay = bus.delay();
705
706 let started = Instant::now();
707 delay.delay_ms(500);
708 delay.delay_us(250);
709
710 assert!(started.elapsed() < Duration::from_millis(250));
711 assert_eq!(bus.waited_micros(), 500_250);
712 }
713
714 #[cfg(not(all(feature = "linux", target_os = "linux")))]
715 #[test]
716 fn opening_an_adapter_needs_linux() {
717 let error = I2cBus::open("/dev/i2c-1").unwrap_err();
718 assert_eq!(error, OpenError::Unsupported);
719 assert!(error.to_string().contains("only Linux"), "{error}");
720 }
721
722 #[cfg(all(feature = "linux", target_os = "linux"))]
723 #[test]
724 fn opening_a_missing_adapter_names_it() {
725 let error = I2cBus::open("/dev/i2c-pamoja-absent").unwrap_err();
726 assert!(
727 error.to_string().starts_with("/dev/i2c-pamoja-absent: "),
728 "{error}"
729 );
730 }
731}