1#![no_std]
14#![macro_use]
15pub(crate) mod fmt;
16
17mod error;
18pub use error::{Error, Result};
19
20#[cfg(not(any(feature = "sync", feature = "async")))]
21compile_error!("You should probably choose at least one of `sync` and `async` features.");
22
23#[cfg(feature = "sync")]
24use embedded_hal::i2c::ErrorType;
25#[cfg(feature = "sync")]
26use embedded_hal::i2c::I2c;
27#[cfg(feature = "async")]
28use embedded_hal_async::i2c::ErrorType as AsyncErrorType;
29#[cfg(feature = "async")]
30use embedded_hal_async::i2c::I2c as AsyncI2c;
31
32#[cfg(any(feature = "async", feature = "sync"))]
34const SLAVE_ADDRESS: u8 = 0b1001000; #[cfg(not(feature = "not-recommended-rfs"))]
37const RECOMMENDED_RFS_MIN: u32 = 40_000;
38#[cfg(not(feature = "not-recommended-rfs"))]
39const RECOMMENDED_RFS_MAX: u32 = 160_000;
40
41const IOUT_UA_MIN: f32 = 50.0;
42const IOUT_UA_MAX: f32 = 200.0;
43
44#[derive(Debug, Clone, Copy)]
46#[cfg_attr(feature = "defmt", derive(defmt::Format))]
47#[repr(u8)]
48pub enum Output {
49 Zero = 0xF8,
50 One = 0xF9,
51}
52
53impl From<Output> for u8 {
54 fn from(value: Output) -> Self {
55 value as u8
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq)]
61#[cfg_attr(feature = "defmt", derive(defmt::Format))]
62pub enum Status {
63 Disable,
65 Sink(u8),
67 SinkMicroAmp(f32),
69 Source(u8),
71 SourceMicroAmp(f32),
73}
74
75impl Status {
76 pub fn code(&self) -> Option<u8> {
92 match self {
93 Self::Sink(c) | Self::Source(c) => Some(*c),
94 Self::Disable => Some(0),
95 _ => None,
96 }
97 }
98
99 pub fn current_ua(&self, rfs_ohm: u32) -> Option<f32> {
114 self.code()
115 .map(|code| ((62_312.5 * code as f64) / (rfs_ohm as f64)) as f32)
116 }
117}
118
119impl From<u8> for Status {
120 fn from(value: u8) -> Self {
121 let sourcing = value & 0x80 == 0x80;
122 let code = value & 0x7F;
123
124 match (sourcing, code) {
125 (true, 0) => Self::Disable,
126 (false, 0) => Self::Disable,
127 (true, c) => Self::Source(c),
128 (false, c) => Self::Sink(c),
129 }
130 }
131}
132
133#[maybe_async_cfg2::maybe(
135 sync(feature = "sync", self = "DS4432"),
136 async(feature = "async", keep_self)
137)]
138pub struct AsyncDS4432<I> {
139 i2c: I,
140 rfs0_ohm: Option<u32>,
141 rfs1_ohm: Option<u32>,
142}
143
144#[maybe_async_cfg2::maybe(
145 sync(
146 feature = "sync",
147 self = "DS4432",
148 idents(AsyncI2c(sync = "I2c"), AsyncErrorType(sync = "ErrorType"))
149 ),
150 async(feature = "async", keep_self)
151)]
152impl<I: AsyncI2c + AsyncErrorType> AsyncDS4432<I> {
153 pub fn new(i2c: I) -> Self {
158 trace!("new");
159 Self::with_rfs(i2c, None, None).unwrap()
160 }
161
162 pub fn with_rfs(
171 i2c: I,
172 rfs0_ohm: Option<u32>,
173 rfs1_ohm: Option<u32>,
174 ) -> Result<Self, I::Error> {
175 for rfs in [rfs0_ohm, rfs1_ohm].into_iter().flatten() {
176 #[cfg(feature = "not-recommended-rfs")]
177 if rfs == 0 {
178 return Err(Error::InvalidRfs);
179 }
180 #[cfg(not(feature = "not-recommended-rfs"))]
181 if !(RECOMMENDED_RFS_MIN..=RECOMMENDED_RFS_MAX).contains(&rfs) {
182 return Err(Error::InvalidRfs);
183 }
184 }
185 Ok(Self {
186 i2c,
187 rfs0_ohm,
188 rfs1_ohm,
189 })
190 }
191
192 pub async fn set_status(&mut self, output: Output, status: Status) -> Result<(), I::Error> {
194 trace!("set_status");
195
196 let reg = output.into();
197 let value = match status {
198 Status::Disable | Status::Sink(0) | Status::Source(0) => 0,
199 Status::Sink(code) => {
200 if code > 127 {
201 return Err(Error::InvalidCode(code));
202 } else {
203 code
204 }
205 }
206 Status::Source(code) => {
207 if code > 127 {
208 return Err(Error::InvalidCode(code));
209 } else {
210 code | 0x80
212 }
213 }
214 Status::SinkMicroAmp(current) => {
215 if !(IOUT_UA_MIN..=IOUT_UA_MAX).contains(¤t) {
216 return Err(Error::InvalidIout);
217 }
218 let rfs = match output {
219 Output::Zero => self.rfs0_ohm.ok_or(Error::UnknownRfs)?,
220 Output::One => self.rfs1_ohm.ok_or(Error::UnknownRfs)?,
221 };
222 ((current * (rfs as f32)) / 62_312.5) as u8
223 }
224 Status::SourceMicroAmp(current) => {
225 if !(IOUT_UA_MIN..=IOUT_UA_MAX).contains(¤t) {
226 return Err(Error::InvalidIout);
227 }
228 let rfs = match output {
229 Output::Zero => self.rfs0_ohm.ok_or(Error::UnknownRfs)?,
230 Output::One => self.rfs1_ohm.ok_or(Error::UnknownRfs)?,
231 };
232 ((current * (rfs as f32)) / 62_312.5) as u8 | 0x80
234 }
235 };
236
237 debug!("W @0x{:x}={:x}", reg, value);
238
239 self.i2c
240 .write(SLAVE_ADDRESS, &[reg, value])
241 .await
242 .map_err(Error::I2c)
243 }
244
245 pub async fn status(&mut self, output: Output) -> Result<Status, I::Error> {
247 trace!("status");
248
249 let mut buf = [0x00];
250 let reg = output.into();
251
252 self.i2c
253 .write_read(SLAVE_ADDRESS, &[reg], &mut buf)
254 .await
255 .map_err(Error::I2c)?;
256
257 debug!("R @0x{:x}={:x}", reg, buf[0]);
258
259 let mut status = buf[0].into();
260 match output {
261 Output::Zero => {
262 if let Some(rfs) = self.rfs0_ohm {
263 status = match status {
264 Status::Sink(code) => {
265 Status::SinkMicroAmp(Status::Sink(code).current_ua(rfs).unwrap())
266 }
267 Status::Source(code) => {
268 Status::SourceMicroAmp(Status::Source(code).current_ua(rfs).unwrap())
269 }
270 _ => status,
271 }
272 }
273 }
274 Output::One => {
275 if let Some(rfs) = self.rfs1_ohm {
276 status = match status {
277 Status::Sink(code) => {
278 Status::SinkMicroAmp(Status::Sink(code).current_ua(rfs).unwrap())
279 }
280 Status::Source(code) => {
281 Status::SourceMicroAmp(Status::Source(code).current_ua(rfs).unwrap())
282 }
283 _ => status,
284 }
285 }
286 }
287 }
288 Ok(status)
289 }
290
291 pub fn release(self) -> I {
293 self.i2c
294 }
295
296 pub fn destroy(self) -> Self {
298 self
299 }
300}
301
302#[cfg(test)]
303mod test {
304 extern crate std;
306
307 use super::*;
308 use embedded_hal_mock::eh1::i2c;
309 use std::vec;
310
311 #[test]
312 fn u8_to_status_conversion() {
313 assert_eq!(Status::from(0x2A), Status::Sink(42));
314 assert_eq!(Status::from(0xAA), Status::Source(42));
315 assert_eq!(Status::from(0x00), Status::Disable);
316 assert_eq!(Status::from(0x80), Status::Disable);
317 }
318
319 #[test]
320 fn can_get_output_0_status() {
321 let expectations = [i2c::Transaction::write_read(
322 SLAVE_ADDRESS,
323 vec![Output::Zero as u8],
324 vec![0xAA],
325 )];
326 let mock = i2c::Mock::new(&expectations);
327 let mut ds4432 = DS4432::new(mock);
328
329 let status = ds4432.status(Output::Zero).unwrap();
330 assert!(matches!(status, Status::Source(42)));
331
332 let mut mock = ds4432.release();
333 mock.done();
334 }
335
336 #[test]
337 fn can_set_output_1_status() {
338 let expectations = [i2c::Transaction::write(
339 SLAVE_ADDRESS,
340 vec![Output::One as u8, 0x2A],
341 )];
342 let mock = i2c::Mock::new(&expectations);
343 let mut ds4432 = DS4432::new(mock);
344
345 ds4432.set_status(Output::One, Status::Sink(42)).unwrap();
347
348 let mut mock = ds4432.release();
349 mock.done();
350 }
351
352 #[test]
353 fn can_get_output_0_status_current() {
354 let expectations = [i2c::Transaction::write_read(
355 SLAVE_ADDRESS,
356 vec![Output::Zero as u8],
357 vec![0xAA],
358 )];
359 let mock = i2c::Mock::new(&expectations);
360 let mut ds4432 = DS4432::with_rfs(mock, Some(80_000), None).unwrap();
361
362 let status = ds4432.status(Output::Zero).unwrap();
363 assert!(matches!(status, Status::SourceMicroAmp(32.71406)));
364
365 let mut mock = ds4432.release();
366 mock.done();
367 }
368
369 #[test]
370 fn can_set_output_1_status_current() {
371 let expectations = [i2c::Transaction::write(
372 SLAVE_ADDRESS,
373 vec![Output::One as u8, 0x70],
374 )];
375 let mock = i2c::Mock::new(&expectations);
376 let mut ds4432 = DS4432::with_rfs(mock, None, Some(80_000)).unwrap();
377
378 ds4432
380 .set_status(Output::One, Status::SinkMicroAmp(88.0))
381 .unwrap();
382
383 let mut mock = ds4432.release();
384 mock.done();
385 }
386}