1use bincode::{Decode, Encode};
2use cu29::prelude::*;
3use cu29::units::si::acceleration::meter_per_second_squared;
4use cu29::units::si::angular_velocity::radian_per_second;
5use cu29::units::si::f32::{
6 Acceleration, AngularVelocity, MagneticFluxDensity, ThermodynamicTemperature,
7};
8use cu29::units::si::magnetic_flux_density::microtesla;
9use cu29::units::si::thermodynamic_temperature::degree_celsius;
10use serde::{Deserialize, Serialize};
11
12#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Encode, Decode, Reflect)]
14pub struct ImuPayload {
15 pub accel_x: Acceleration,
16 pub accel_y: Acceleration,
17 pub accel_z: Acceleration,
18 pub gyro_x: AngularVelocity,
19 pub gyro_y: AngularVelocity,
20 pub gyro_z: AngularVelocity,
21 pub temperature: ThermodynamicTemperature,
22}
23
24impl Default for ImuPayload {
25 fn default() -> Self {
26 Self {
27 accel_x: Acceleration::new::<meter_per_second_squared>(0.0),
28 accel_y: Acceleration::new::<meter_per_second_squared>(0.0),
29 accel_z: Acceleration::new::<meter_per_second_squared>(0.0),
30 gyro_x: AngularVelocity::new::<radian_per_second>(0.0),
31 gyro_y: AngularVelocity::new::<radian_per_second>(0.0),
32 gyro_z: AngularVelocity::new::<radian_per_second>(0.0),
33 temperature: ThermodynamicTemperature::new::<degree_celsius>(0.0),
34 }
35 }
36}
37
38impl ImuPayload {
39 pub fn from_raw(accel_mps2: [f32; 3], gyro_rad: [f32; 3], temperature_c: f32) -> Self {
45 let [accel_x, accel_y, accel_z] =
46 accel_mps2.map(Acceleration::new::<meter_per_second_squared>);
47 let [gyro_x, gyro_y, gyro_z] = gyro_rad.map(AngularVelocity::new::<radian_per_second>);
48 let temperature = ThermodynamicTemperature::new::<degree_celsius>(temperature_c);
49
50 Self {
51 accel_x,
52 accel_y,
53 accel_z,
54 gyro_x,
55 gyro_y,
56 gyro_z,
57 temperature,
58 }
59 }
60
61 pub fn from_units(
63 accel_x: Acceleration,
64 accel_y: Acceleration,
65 accel_z: Acceleration,
66 gyro_x: AngularVelocity,
67 gyro_y: AngularVelocity,
68 gyro_z: AngularVelocity,
69 temperature: ThermodynamicTemperature,
70 ) -> Self {
71 Self {
72 accel_x,
73 accel_y,
74 accel_z,
75 gyro_x,
76 gyro_y,
77 gyro_z,
78 temperature,
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
85pub struct MagnetometerPayload {
86 pub mag_x: MagneticFluxDensity,
87 pub mag_y: MagneticFluxDensity,
88 pub mag_z: MagneticFluxDensity,
89}
90
91impl Default for MagnetometerPayload {
92 fn default() -> Self {
93 Self {
94 mag_x: MagneticFluxDensity::new::<microtesla>(0.0),
95 mag_y: MagneticFluxDensity::new::<microtesla>(0.0),
96 mag_z: MagneticFluxDensity::new::<microtesla>(0.0),
97 }
98 }
99}
100
101impl MagnetometerPayload {
102 pub fn from_raw(mag_ut: [f32; 3]) -> Self {
104 let [mag_x, mag_y, mag_z] = mag_ut.map(MagneticFluxDensity::new::<microtesla>);
105 Self {
106 mag_x,
107 mag_y,
108 mag_z,
109 }
110 }
111
112 pub fn from_units(
114 mag_x: MagneticFluxDensity,
115 mag_y: MagneticFluxDensity,
116 mag_z: MagneticFluxDensity,
117 ) -> Self {
118 Self {
119 mag_x,
120 mag_y,
121 mag_z,
122 }
123 }
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, Encode, Decode)]
128pub struct ImuWithMagPayload {
129 pub imu: ImuPayload,
130 pub mag: Option<MagnetometerPayload>,
131}
132
133impl ImuWithMagPayload {
134 pub fn new(imu: ImuPayload, mag: Option<MagnetometerPayload>) -> Self {
135 Self { imu, mag }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use bincode::config;
143
144 #[test]
145 fn round_trip_encode_decode() {
146 let payload = ImuPayload::from_raw([9.8, -2.0, 0.5], [0.01, -0.02, 0.5], 36.5);
147
148 let cfg = config::standard();
149 let mut buffer = [0u8; 128];
150 let len = bincode::encode_into_slice(payload, &mut buffer, cfg).unwrap();
151 let (decoded, used) =
152 bincode::decode_from_slice::<ImuPayload, _>(&buffer[..len], cfg).unwrap();
153
154 assert_eq!(used, len);
155 assert_eq!(decoded.accel_x.value, payload.accel_x.value);
156 assert_eq!(decoded.gyro_y.value, payload.gyro_y.value);
157 assert_eq!(
158 decoded.temperature.get::<degree_celsius>(),
159 payload.temperature.get::<degree_celsius>()
160 );
161 }
162
163 #[test]
164 fn builds_from_units() {
165 let accel = Acceleration::new::<meter_per_second_squared>(9.81);
166 let gyro = AngularVelocity::new::<radian_per_second>(0.25);
167 let temp = ThermodynamicTemperature::new::<degree_celsius>(20.0);
168
169 let payload = ImuPayload::from_units(accel, accel, accel, gyro, gyro, gyro, temp);
170
171 assert_eq!(payload.accel_x.value, accel.value);
172 assert_eq!(payload.gyro_z.value, gyro.value);
173 }
174
175 #[test]
176 fn magnetometer_round_trip() {
177 let mag_payload = MagnetometerPayload::from_raw([42.0, -13.0, 8.0]);
178 let cfg = config::standard();
179 let mut buffer = [0u8; 128];
180 let len = bincode::encode_into_slice(mag_payload, &mut buffer, cfg).unwrap();
181 let (decoded, used) =
182 bincode::decode_from_slice::<MagnetometerPayload, _>(&buffer[..len], cfg).unwrap();
183
184 assert_eq!(used, len);
185 assert_eq!(decoded.mag_x.value, mag_payload.mag_x.value);
186 assert_eq!(decoded.mag_z.value, mag_payload.mag_z.value);
187 }
188
189 #[test]
190 fn combined_payload_handles_optional_mag() {
191 let imu = ImuPayload::from_raw([1.0, 2.0, 3.0], [4.0, 5.0, 6.0], 22.0);
192 let mag = MagnetometerPayload::from_raw([7.0, 8.0, 9.0]);
193 let combined = ImuWithMagPayload::new(imu, Some(mag));
194
195 let cfg = config::standard();
196 let mut buffer = [0u8; 256];
197 let len = bincode::encode_into_slice(combined, &mut buffer, cfg).unwrap();
198 let (decoded, used) =
199 bincode::decode_from_slice::<ImuWithMagPayload, _>(&buffer[..len], cfg).unwrap();
200
201 assert_eq!(used, len);
202 assert_eq!(decoded.imu.accel_y.value, imu.accel_y.value);
203 assert_eq!(decoded.mag.unwrap().mag_y.value, mag.mag_y.value);
204 }
205}