hs_hackathon_car/
lib.rs

1#[cfg(target_os = "linux")]
2mod raw;
3
4#[cfg(target_os = "linux")]
5use linux_embedded_hal::I2cdev;
6#[cfg(target_os = "linux")]
7use pwm_pca9685::Pca9685;
8#[cfg(target_os = "linux")]
9use raw::dc::DcKit;
10#[cfg(target_os = "linux")]
11use raw::servo::ServoKit;
12#[cfg(target_os = "linux")]
13use raw::{init_motor_pwm, init_servo_pwm, Motor};
14#[cfg(target_os = "linux")]
15use std::ops::Drop;
16
17use std::time::{Duration, Instant};
18
19#[derive(PartialEq, PartialOrd)]
20pub struct Velocity(f32);
21
22impl Velocity {
23    pub fn backward() -> Self {
24        Self(-100.0)
25    }
26
27    pub fn none() -> Self {
28        Self(0.0)
29    }
30
31    pub fn forward() -> Self {
32        Self(100.0)
33    }
34
35    pub fn into_inner(self) -> f32 {
36        self.0
37    }
38}
39
40impl TryFrom<f32> for Velocity {
41    type Error = eyre::Report;
42
43    fn try_from(value: f32) -> eyre::Result<Velocity> {
44        eyre::ensure!(
45            value >= Velocity::backward().0 && value <= Velocity::forward().0,
46            "velocity must be between {} and {}",
47            Velocity::backward().0,
48            Velocity::forward().0,
49        );
50        Ok(Velocity(value))
51    }
52}
53
54impl Default for Velocity {
55    fn default() -> Self {
56        Self(0.0)
57    }
58}
59
60/// The motor of the car
61pub struct MotorSocket {
62    #[cfg(target_os = "linux")]
63    dc_motor: DcKit,
64    #[cfg(target_os = "linux")]
65    dc_pwm: Pca9685<I2cdev>,
66    cooldown_since: Instant,
67}
68
69impl MotorSocket {
70    /// Open the pins to talk to the car's motor
71    pub async fn open() -> eyre::Result<Self> {
72        #[cfg(target_os = "linux")]
73        let mut dc_pwm = init_motor_pwm(None)?;
74        #[cfg(target_os = "linux")]
75        let dc_motor = DcKit::try_new(&mut dc_pwm, Motor::Motor1)?;
76        Ok(MotorSocket {
77            #[cfg(target_os = "linux")]
78            dc_pwm,
79            #[cfg(target_os = "linux")]
80            dc_motor,
81            cooldown_since: Instant::now(),
82        })
83    }
84
85    /// Set the velocity of the motor
86    ///
87    /// Car will not move for longer than 1 second.
88    pub async fn move_for(&mut self, velocity: Velocity, max_dur: Duration) -> eyre::Result<()> {
89        if let Some(left) = Duration::from_secs(3).checked_sub(self.cooldown_since.elapsed()) {
90            tokio::time::sleep(left).await;
91        }
92        // -1 because we wired it backwards
93        #[cfg(target_os = "linux")]
94        self.dc_motor
95            .set_throttle(&mut self.dc_pwm, -1. * velocity.into_inner() / 100.0)?;
96        #[cfg(not(target_os = "linux"))]
97        let _ = velocity; // just for unused variables
98        let actual_dur = std::cmp::min(Duration::from_secs(1), max_dur);
99        tokio::time::sleep(actual_dur).await;
100        #[cfg(target_os = "linux")]
101        self.dc_motor
102            .set_throttle(&mut self.dc_pwm, Velocity::none().into_inner() / 100.0)?;
103        self.cooldown_since = Instant::now();
104        Ok(())
105    }
106
107    /// Stop the motor.
108    ///
109    /// Note that once this is called, the motor will not start again without re-initialization.
110    pub fn stop(&mut self) -> eyre::Result<()> {
111        #[cfg(target_os = "linux")]
112        self.dc_motor.stop(&mut self.dc_pwm)?;
113        Ok(())
114    }
115}
116
117impl Drop for MotorSocket {
118    fn drop(&mut self) {
119        let _ = self.stop();
120    }
121}
122
123/// Discrete wrapper of the wheel orientation
124pub struct WheelOrientation {
125    #[cfg(target_os = "linux")]
126    servo: ServoKit,
127    #[cfg(target_os = "linux")]
128    servo_pwm: Pca9685<I2cdev>,
129    current: Angle,
130}
131
132impl WheelOrientation {
133    /// Open the pins to talk to the car's servo
134    pub async fn new() -> eyre::Result<Self> {
135        #[cfg(target_os = "linux")]
136        let mut servo_pwm = init_servo_pwm(None)?;
137        #[cfg(target_os = "linux")]
138        let servo = ServoKit::try_new(&mut servo_pwm, Motor::Servo)?;
139        Ok(WheelOrientation {
140            #[cfg(target_os = "linux")]
141            servo_pwm,
142            #[cfg(target_os = "linux")]
143            servo,
144            current: Default::default(),
145        })
146    }
147
148    /// Retrieve the current orientation of the wheels
149    pub async fn current(self) -> eyre::Result<Angle> {
150        Ok(self.current)
151    }
152
153    /// Set the wheel orientation to a specific value
154    pub async fn set(&mut self, angle: Angle) -> eyre::Result<()> {
155        self.current = angle;
156        let raw: f32 = (-90.0 * angle.into_inner() + 90.0).try_into()?;
157        #[cfg(not(target_os = "linux"))]
158        let _ = raw;
159        #[cfg(target_os = "linux")]
160        self.servo.set_angle(&mut self.servo_pwm, raw)?;
161        Ok(())
162    }
163}
164
165impl Drop for WheelOrientation {
166    fn drop(&mut self) {
167        #[cfg(target_os = "linux")]
168        let _ = self.servo.set_angle(&mut self.servo_pwm, 90.0);
169    }
170}
171
172#[derive(PartialEq, PartialOrd, Copy, Clone)]
173pub struct Angle(f32);
174
175impl Angle {
176    pub fn left() -> Self {
177        Self(-1.0)
178    }
179
180    pub fn straight() -> Self {
181        Self(0.0)
182    }
183
184    pub fn right() -> Self {
185        Self(1.0)
186    }
187
188    pub fn into_inner(self) -> f32 {
189        self.0
190    }
191}
192
193impl TryFrom<f32> for Angle {
194    type Error = eyre::Report;
195
196    fn try_from(value: f32) -> eyre::Result<Angle> {
197        eyre::ensure!(
198            value >= Angle::left().0 && value <= Angle::right().0,
199            "angle must be between {} and {}",
200            Angle::left().0,
201            Angle::right().0,
202        );
203        Ok(Angle(value))
204    }
205}
206
207impl Default for Angle {
208    fn default() -> Self {
209        Self::straight()
210    }
211}