1use bincode::{Decode, Encode};
2#[cfg(hardware)]
3use cu_linux_resources::LinuxI2c;
4use cu29::prelude::*;
5#[cfg(hardware)]
6use cu29::resource::Owned;
7use cu29::resource::{ResourceBindingMap, ResourceBindings, ResourceManager};
8use cu29::units::si::acceleration::standard_gravity;
9use cu29::units::si::angle::degree;
10use cu29::units::si::angular_velocity::degree_per_second;
11use cu29::units::si::f32::{Acceleration, Angle, AngularVelocity, MagneticFluxDensity};
12use cu29::units::si::magnetic_flux_density::nanotesla;
13#[cfg(hardware)]
14use embedded_hal::i2c::I2c;
15use std::fmt::Display;
16
17#[allow(unused)]
18const WT901_I2C_ADDRESS: u8 = 0x50;
19
20#[allow(unused)]
21#[repr(u8)]
22#[derive(Debug, Clone, Copy)]
23enum Registers {
24 AccX = 0x34,
26 AccY = 0x35,
27 AccZ = 0x36,
28
29 GyroX = 0x37,
31 GyroY = 0x38,
32 GyroZ = 0x39,
33
34 MagX = 0x3A,
36 MagY = 0x3B,
37 MagZ = 0x3C,
38
39 Roll = 0x3D,
41 Pitch = 0x3E,
42 Yaw = 0x3F,
43}
44
45impl Registers {
46 #[allow(dead_code)]
47 fn offset(&self) -> usize {
48 ((*self as u8 - Registers::AccX as u8) * 2) as usize
49 }
50}
51
52use cu29_log_derive::debug;
53use cu29_traits::CuError;
54use serde::{Deserialize, Serialize};
55
56#[derive(Reflect)]
57#[reflect(from_reflect = false)]
58pub struct WT901 {
59 #[cfg(hardware)]
60 #[reflect(ignore)]
61 i2c: LinuxI2c,
62}
63
64#[derive(Copy, Clone, Debug, Eq, PartialEq)]
65pub enum Binding {
66 I2c,
67}
68
69pub struct Wt901Resources {
70 #[cfg(hardware)]
71 pub i2c: Owned<LinuxI2c>,
72}
73
74impl<'r> ResourceBindings<'r> for Wt901Resources {
75 type Binding = Binding;
76
77 fn from_bindings(
78 manager: &'r mut ResourceManager,
79 mapping: Option<&ResourceBindingMap<Self::Binding>>,
80 ) -> CuResult<Self> {
81 #[cfg(hardware)]
82 {
83 let mapping = mapping.ok_or_else(|| {
84 CuError::from("WT901 requires an `i2c` resource mapping in copperconfig")
85 })?;
86 let path = mapping.get(Binding::I2c).ok_or_else(|| {
87 CuError::from("WT901 resources must include `i2c: <bundle.resource>`")
88 })?;
89 let i2c = manager
90 .take::<LinuxI2c>(path.typed())
91 .map_err(|e| e.add_cause("Failed to fetch WT901 I2C resource"))?;
92 Ok(Self { i2c })
93 }
94 #[cfg(mock)]
95 {
96 let _ = manager;
97 let _ = mapping;
98 Ok(Self {})
99 }
100 }
101}
102
103#[derive(Default, Clone, Debug, Serialize, Deserialize, Encode, Decode, Reflect)]
104pub struct PositionalReadingsPayload {
105 acc_x: Acceleration,
106 acc_y: Acceleration,
107 acc_z: Acceleration,
108 gyro_x: AngularVelocity,
109 gyro_y: AngularVelocity,
110 gyro_z: AngularVelocity,
111 mag_x: MagneticFluxDensity,
112 mag_y: MagneticFluxDensity,
113 mag_z: MagneticFluxDensity,
114 roll: Angle,
115 pitch: Angle,
116 yaw: Angle,
117}
118
119impl Display for PositionalReadingsPayload {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 write!(
122 f,
123 "acc_x: {} g, acc_y: {} g, acc_z: {} g\n gyro_x: {} deg/s, gyro_y: {} deg/s, gyro_z: {} deg/s\nmag_x: {} nT, mag_y: {} nT, mag_z: {} nT\nroll: {} deg, pitch: {} deg, yaw: {} deg",
124 self.acc_x.get::<standard_gravity>(),
125 self.acc_y.get::<standard_gravity>(),
126 self.acc_z.get::<standard_gravity>(),
127 self.gyro_x.get::<degree_per_second>(),
128 self.gyro_y.get::<degree_per_second>(),
129 self.gyro_z.get::<degree_per_second>(),
130 self.mag_x.get::<nanotesla>(),
131 self.mag_y.get::<nanotesla>(),
132 self.mag_z.get::<nanotesla>(),
133 self.roll.get::<degree>(),
134 self.pitch.get::<degree>(),
135 self.yaw.get::<degree>()
136 )
137 }
138}
139
140#[allow(unused)]
142const REGISTER_SPAN_SIZE: usize = ((Registers::Yaw as u8 - Registers::AccX as u8) * 2 + 2) as usize;
143
144#[allow(unused)]
145impl WT901 {
146 fn bulk_position_read(&mut self, pr: &mut PositionalReadingsPayload) -> Result<(), CuError> {
147 debug!("Trying to read i2c");
148
149 #[cfg(hardware)]
150 {
151 let mut buf = [0u8; REGISTER_SPAN_SIZE];
152 self.i2c
153 .write_read(WT901_I2C_ADDRESS, &[Registers::AccX as u8], &mut buf)
154 .expect("Error reading WT901");
155 pr.acc_x = convert_acc(get_vec_i16(&buf, Registers::AccX.offset()));
156 pr.acc_y = convert_acc(get_vec_i16(&buf, Registers::AccY.offset()));
157 pr.acc_z = convert_acc(get_vec_i16(&buf, Registers::AccZ.offset()));
158 pr.gyro_x = convert_ang_vel(get_vec_i16(&buf, Registers::GyroX.offset()));
159 pr.gyro_y = convert_ang_vel(get_vec_i16(&buf, Registers::GyroY.offset()));
160 pr.gyro_z = convert_ang_vel(get_vec_i16(&buf, Registers::GyroZ.offset()));
161 pr.mag_x = convert_mag(get_vec_i16(&buf, Registers::MagX.offset()));
162 pr.mag_y = convert_mag(get_vec_i16(&buf, Registers::MagY.offset()));
163 pr.mag_z = convert_mag(get_vec_i16(&buf, Registers::MagZ.offset()));
164 pr.roll = convert_angle(get_vec_i16(&buf, Registers::Roll.offset()));
165 pr.pitch = convert_angle(get_vec_i16(&buf, Registers::Pitch.offset()));
166 pr.yaw = convert_angle(get_vec_i16(&buf, Registers::Yaw.offset()));
167 }
168 Ok(())
169 }
170}
171
172impl Freezable for WT901 {
173 }
175
176impl CuSrcTask for WT901 {
177 type Resources<'r> = Wt901Resources;
178 type Output<'m> = output_msg!(PositionalReadingsPayload);
179
180 fn new(_config: Option<&ComponentConfig>, _resources: Self::Resources<'_>) -> CuResult<Self>
181 where
182 Self: Sized,
183 {
184 let _ = _config;
185 #[cfg(hardware)]
186 let i2c = _resources.i2c.0;
187 Ok(WT901 {
188 #[cfg(hardware)]
189 i2c,
190 })
191 }
192
193 fn process(&mut self, _clock: &RobotClock, new_msg: &mut Self::Output<'_>) -> CuResult<()> {
194 let mut pos = PositionalReadingsPayload::default();
195 self.bulk_position_read(&mut pos)?;
196 new_msg.set_payload(pos);
197 Ok(())
198 }
199}
200
201#[inline]
203#[allow(dead_code)]
204fn get_vec_u16(buf: &[u8], offset: usize) -> u16 {
205 u16::from_le_bytes([buf[offset], buf[offset + 1]])
206}
207
208#[inline]
210#[allow(dead_code)]
211fn get_vec_i16(buf: &[u8], offset: usize) -> i16 {
212 i16::from_le_bytes([buf[offset], buf[offset + 1]])
213}
214
215#[allow(dead_code)]
216fn convert_acc(acc: i16) -> Acceleration {
217 let acc = acc as f32 / 32768.0 * 16.0;
219 Acceleration::new::<standard_gravity>(acc)
220}
221
222#[allow(dead_code)]
223fn convert_ang_vel(angv: i16) -> AngularVelocity {
224 let acc = (angv as f32 / 32768.0) * 2000.0;
226 AngularVelocity::new::<degree_per_second>(acc)
227}
228
229#[allow(dead_code)]
230fn convert_mag(mag: i16) -> MagneticFluxDensity {
231 let mag = (mag as f32 / 32768.0) * 8.333;
233 MagneticFluxDensity::new::<nanotesla>(mag)
234}
235
236#[allow(dead_code)]
237fn convert_angle(angle: i16) -> Angle {
238 let angle = angle as f32 / 32768.0 * 180.0;
239 Angle::new::<degree>(angle)
240}