fll_rs/lego/
robot_impl.rs

1use crate::error::Result;
2use crate::input::Input;
3use crate::movement::controller::MovementController;
4use crate::movement::pid::PidConfig;
5use crate::movement::spec::RobotSpec;
6use crate::robot::{
7    AngleProvider, ColorSensor, Command, Motor, MotorId, Robot, StopAction, TurnType,
8};
9use crate::types::{Degrees, DegreesPerSecond, Distance, Heading, Percent, Speed};
10use anyhow::{bail, Context};
11use ev3dev_lang_rust::motors::{MotorPort, TachoMotor as Ev3TachoMotor};
12use ev3dev_lang_rust::sensors::ColorSensor as Ev3ColorSensor;
13use ev3dev_lang_rust::sensors::GyroSensor as Ev3GyroSensor;
14use ev3dev_lang_rust::sensors::Sensor;
15use ev3dev_lang_rust::sensors::SensorPort;
16use ev3dev_lang_rust::{wait, Ev3Button, Port, PowerSupply};
17use ev3dev_lang_rust::{Attribute, Device};
18use fxhash::FxHashMap as HashMap;
19use std::cell::{RefCell, RefMut};
20use std::fs::File;
21use std::os::unix::io::AsRawFd;
22use std::rc::Rc;
23use std::thread;
24use std::time::Duration;
25
26pub struct LegoRobot {
27    battery: PowerSupply,
28
29    buttons: Ev3Button,
30    buttons_raw: File,
31    input: RefCell<Input>,
32
33    pid_config: PidConfig,
34    controller: RefCell<MovementController>,
35    angle_algorithm: AngleAlgorithm,
36
37    motors: HashMap<MotorId, RefCell<LegoMotor>>,
38    motor_definitions: Vec<(MotorId, MotorPort)>,
39    sensors: HashMap<String, LegoSensor>,
40
41    spec: Rc<RobotSpec>,
42}
43
44pub enum AngleAlgorithm {
45    Wheel,
46    Gyro(SensorPort),
47}
48
49enum LegoSensor {
50    Gyro(RefCell<LegoGyroSensor>),
51    Color(RefCell<LegoColorSensor>),
52}
53
54struct LegoMotor {
55    motor: Ev3TachoMotor,
56
57    queued_command: Option<Command>,
58
59    stopping_action: Option<StopAction>,
60
61    speed_sp: Option<i32>,
62    time_sp: Option<Duration>,
63    position_sp: Option<i32>,
64
65    spec: Rc<RobotSpec>,
66}
67
68struct LegoGyroSensor {
69    gyro: Ev3GyroSensor,
70}
71
72struct LegoColorSensor {
73    color: Ev3ColorSensor,
74    white: f32,
75    black: f32,
76}
77
78impl LegoRobot {
79    pub fn new(
80        motor_definitions: &[(MotorId, MotorPort)],
81        pid_config: PidConfig,
82        spec: RobotSpec,
83        angle_algorithm: AngleAlgorithm,
84    ) -> Result<Self> {
85        // Wait for any drivers to start up
86        thread::sleep(Duration::from_millis(300));
87
88        let battery = PowerSupply::new().context("Couldn't find power supply")?;
89
90        let controller = MovementController::new(pid_config).into();
91
92        let spec = Rc::new(spec);
93
94        // Setup input
95        let buttons = Ev3Button::new().context("Couldn't find buttons")?;
96        let input = Default::default();
97        let buttons_raw = File::open("/dev/input/by-path/platform-gpio_keys-event")?;
98
99        // Discover sensors and motors
100        let motor_definitions: Vec<_> = motor_definitions.into();
101        let motors =
102            discover_motors(&motor_definitions, spec.clone()).context("Discover motors")?;
103        let sensors = discover_sensors().context("Discover sensors")?;
104
105        Ok(Self {
106            battery,
107            buttons,
108            input,
109            buttons_raw,
110            controller,
111            motors,
112            spec,
113            angle_algorithm,
114            sensors,
115            motor_definitions,
116            pid_config,
117        })
118    }
119
120    pub fn rediscover_motors(&mut self) -> Result<()> {
121        self.motors = discover_motors(&self.motor_definitions, self.spec.clone())
122            .context("Discover motors")?;
123
124        Ok(())
125    }
126
127    pub fn rediscover_sensors(&mut self) -> Result<()> {
128        self.sensors = discover_sensors().context("Discover sensors")?;
129
130        Ok(())
131    }
132
133    fn update_button_state(&self) -> bool {
134        self.buttons.process();
135
136        let mut state = [false; 6];
137
138        state[Input::UP] = self.buttons.is_up();
139        state[Input::DOWN] = self.buttons.is_down();
140        state[Input::LEFT] = self.buttons.is_left();
141        state[Input::RIGHT] = self.buttons.is_right();
142        state[Input::ENTER] = self.buttons.is_enter();
143        state[Input::BACKSPACE] = self.buttons.is_backspace();
144
145        self.input.borrow_mut().update(state)
146    }
147}
148
149impl Robot for LegoRobot {
150    fn drive(&self, distance: impl Into<Distance>, speed: impl Into<Speed>) -> Result<()> {
151        self.controller.borrow_mut().drive(
152            self,
153            distance.into().to_deg(&self.spec),
154            speed.into().to_dps(&self.spec),
155        )
156    }
157
158    fn turn(&self, angle: Heading, speed: impl Into<Speed>) -> Result<()> {
159        self.controller
160            .borrow_mut()
161            .turn(self, angle, speed.into().to_dps(&self.spec))
162    }
163
164    fn turn_named(&self, angle: Heading, speed: impl Into<Speed>, turn: TurnType) -> Result<()> {
165        self.controller
166            .borrow_mut()
167            .turn_named(self, angle, speed.into().to_dps(&self.spec), turn)
168    }
169
170    fn motor(&self, motor_id: MotorId) -> Option<RefMut<dyn Motor>> {
171        let motor = self.motors.get(&motor_id);
172
173        if let Some(motor) = motor {
174            Some(motor.borrow_mut())
175        } else {
176            None
177        }
178    }
179
180    fn color_sensor(&self, port: SensorPort) -> Option<RefMut<dyn ColorSensor>> {
181        let sensor = self.sensors.get(&port.address());
182
183        if let Some(LegoSensor::Color(color)) = sensor {
184            Some(color.borrow_mut())
185        } else {
186            None
187        }
188    }
189
190    fn process_buttons(&self) -> Result<Input> {
191        self.update_button_state();
192
193        Ok(self.input.borrow().clone())
194    }
195
196    fn battery(&self) -> Result<Percent> {
197        // let min = self
198        //     .battery
199        //     .get_voltage_min_design()
200        //     .context("Get min voltage")? as f32;
201
202        let max = self
203            .battery
204            .get_voltage_max_design()
205            .context("Get max voltage")? as f32;
206        let current = self.battery.get_voltage_now().context("Get voltage")? as f32;
207
208        // Ok(Percent((dbg!(current) - dbg!(min)) / (dbg!(max) - min)))
209
210        // Seems to be a bug in ev3dev
211        Ok(Percent((current * 10.0 / max).min(1.0).max(0.0)))
212    }
213
214    fn await_input(&self, timeout: Option<Duration>) -> Result<Input> {
215        wait::wait(
216            self.buttons_raw.as_raw_fd(),
217            || self.update_button_state(),
218            timeout,
219        );
220
221        Ok(self.input.borrow().clone())
222    }
223
224    fn stop(&self) -> Result<()> {
225        for motor in self.motors.values() {
226            motor
227                .borrow_mut()
228                .motor_reset(Some(StopAction::Coast))
229                .context("Reset motor")?;
230        }
231        Ok(())
232    }
233
234    fn reset(&self) -> Result<()> {
235        for motor in self.motors.values() {
236            motor
237                .borrow_mut()
238                .motor_reset(None)
239                .context("Reset motor")?;
240        }
241        for sensor in self.sensors.values() {
242            match sensor {
243                LegoSensor::Gyro(gyro) => {
244                    reset_gyro_soft(&mut gyro.borrow_mut())?;
245                }
246                LegoSensor::Color(_) => {}
247            }
248        }
249        *self.input.borrow_mut() = Default::default();
250        *self.controller.borrow_mut() = MovementController::new(self.pid_config);
251
252        Ok(())
253    }
254
255    fn spec(&self) -> &RobotSpec {
256        &self.spec
257    }
258
259    fn set_heading(&self, angle: Heading) -> Result<()> {
260        self.controller.borrow_mut().target_direction = angle;
261        Ok(())
262    }
263}
264
265impl AngleProvider for LegoRobot {
266    fn angle(&self) -> Result<Heading> {
267        match self.angle_algorithm {
268            AngleAlgorithm::Wheel => {
269                let left = self
270                    .motor(MotorId::DriveLeft)
271                    .context("Left motor")?
272                    .motor_angle()?;
273                let right = self
274                    .motor(MotorId::DriveRight)
275                    .context("Right motor")?
276                    .motor_angle()?;
277
278                Ok(self.spec().get_approx_angle(left, right))
279            }
280            AngleAlgorithm::Gyro(port) => {
281                let sensor = self.sensors.get(&port.address()).context("No gyro found")?;
282
283                if let LegoSensor::Gyro(gyro) = sensor {
284                    Ok(Heading(gyro.borrow().gyro.get_value0()? as f32))
285                } else {
286                    bail!("Sensor in {port:?} is not a gyro sensor");
287                }
288            }
289        }
290    }
291}
292
293impl LegoMotor {
294    fn handle_command(&mut self, command: &Command, execute: bool) -> Result<()> {
295        match command {
296            Command::On(speed) => {
297                let dps = speed.to_dps(&self.spec).0 as i32;
298
299                if Some(dps) != self.speed_sp {
300                    self.motor.set_speed_sp(dps).context("Set speed setpoint")?;
301                    self.speed_sp = Some(dps);
302                }
303
304                if execute {
305                    self.motor.run_forever().context("Run forever")?;
306                }
307            }
308            Command::Stop(action) => {
309                if Some(*action) != self.stopping_action {
310                    self.motor
311                        .set_stop_action(action.to_str())
312                        .context("Set stop action")?;
313                    self.stopping_action = Some(*action);
314                }
315
316                if execute {
317                    self.motor.stop().context("Stop motor")?;
318                }
319            }
320            Command::Distance(distance, speed) => {
321                let deg = distance.to_deg(&self.spec).0 as i32;
322                let dps = speed.to_dps(&self.spec).0 as i32;
323
324                if Some(deg) != self.position_sp {
325                    self.motor
326                        .set_position_sp(deg)
327                        .context("Set position setpoint")?;
328                    self.position_sp = Some(deg);
329                }
330
331                if Some(dps) != self.speed_sp {
332                    self.motor.set_speed_sp(dps).context("Set speed setpoint")?;
333                    self.speed_sp = Some(dps);
334                }
335
336                if execute {
337                    self.motor.run_to_rel_pos(None).context("Run relative")?;
338                }
339            }
340            Command::To(position, speed) => {
341                let deg = position.to_deg(&self.spec).0 as i32;
342                let dps = speed.to_dps(&self.spec).0 as i32;
343
344                if Some(deg) != self.position_sp {
345                    self.motor
346                        .set_position_sp(deg)
347                        .context("Set position setpoint")?;
348                    self.position_sp = Some(deg);
349                }
350
351                if Some(dps) != self.speed_sp {
352                    self.motor.set_speed_sp(dps).context("Set speed setpoint")?;
353                    self.speed_sp = Some(dps);
354                }
355
356                if execute {
357                    self.motor.run_to_abs_pos(None).context("Run absloute")?;
358                }
359            }
360            Command::Time(duration, speed) => {
361                let dps = speed.to_dps(&self.spec).0 as i32;
362
363                if Some(*duration) != self.time_sp {
364                    self.motor
365                        .set_time_sp(duration.as_millis() as i32)
366                        .context("Set time setpoint")?;
367                    self.time_sp = Some(*duration);
368                }
369
370                if Some(dps) != self.speed_sp {
371                    self.motor.set_speed_sp(dps).context("Set speed setpoint")?;
372                    self.speed_sp = Some(dps)
373                }
374
375                if execute {
376                    self.motor.run_timed(None).context("Run timed")?;
377                }
378            }
379            command => panic!("Got bad command {command:?}"),
380        }
381
382        Ok(())
383    }
384}
385
386impl Motor for LegoMotor {
387    fn raw(&mut self, command: Command) -> Result<()> {
388        // Clear queued action if needed
389        match command {
390            Command::Queue(_) | Command::Execute => {}
391            _ => {
392                self.queued_command = None;
393            }
394        }
395
396        match command {
397            Command::Queue(queued_command) => {
398                self.handle_command(&queued_command, false)?;
399
400                self.queued_command = Some(*queued_command);
401            }
402            Command::Execute => {
403                if let Some(queued_command) = self.queued_command.take() {
404                    self.handle_command(&queued_command, true)?;
405                } else {
406                    bail!("Reveived `Command::Execute` when no command was queued");
407                }
408            }
409            command => {
410                self.handle_command(&command, true)?;
411            }
412        }
413
414        Ok(())
415    }
416
417    fn motor_reset(&mut self, stop_action: Option<StopAction>) -> Result<()> {
418        self.motor.reset().context("Reset motor")?;
419
420        self.queued_command = None;
421        self.stopping_action = None;
422        self.speed_sp = None;
423        self.time_sp = None;
424        self.position_sp = None;
425
426        if let Some(stop_action) = stop_action {
427            self.motor
428                .set_stop_action(stop_action.to_str())
429                .context("Set stop action")?;
430
431            self.stopping_action = Some(stop_action);
432        }
433
434        self.motor.stop().context("Stop motor")?;
435
436        Ok(())
437    }
438
439    fn wait(&self, timeout: Option<Duration>) -> Result<()> {
440        self.motor.wait_until_not_moving(timeout);
441
442        Ok(())
443    }
444
445    fn speed(&self) -> Result<Speed> {
446        self.motor
447            .get_speed()
448            .map(|it| DegreesPerSecond(it as f32).into())
449            .context("Read motor speed")
450    }
451
452    fn motor_angle(&self) -> Result<Distance> {
453        self.motor
454            .get_position()
455            .map(|it| Degrees(it as f32).into())
456            .context("Read motor position")
457    }
458}
459
460impl ColorSensor for LegoColorSensor {
461    fn reflected_light(&self) -> Result<Percent> {
462        let reflected = self.color.get_color().context("Read color sensor")?;
463        let percent = reflected as f32 / 100.0;
464        let adjusted = (percent - self.black) / (self.white - self.black);
465
466        Ok(Percent(adjusted))
467    }
468
469    fn cal_white(&mut self) -> Result<()> {
470        let reflected = self.color.get_color().context("Read color sensor")?;
471        self.white = reflected as f32 / 100.0;
472
473        Ok(())
474    }
475
476    fn cal_black(&mut self) -> Result<()> {
477        let reflected = self.color.get_color().context("Read color sensor")?;
478        self.black = reflected as f32 / 100.0;
479
480        Ok(())
481    }
482
483    fn reset(&mut self) -> Result<()> {
484        self.white = 1.0;
485        self.black = 0.0;
486
487        Ok(())
488    }
489}
490
491fn discover_motors(
492    motor_definitions: &[(MotorId, MotorPort)],
493    spec: Rc<RobotSpec>,
494) -> Result<HashMap<MotorId, RefCell<LegoMotor>>> {
495    let mut motors = HashMap::default();
496
497    for (motor_id, port) in motor_definitions {
498        let motor = Ev3TachoMotor::get(*port)
499            .with_context(|| format!("Couldn't find motor {:?}, port {:?}", motor_id, port))?;
500        motors.insert(
501            *motor_id,
502            LegoMotor {
503                motor,
504                queued_command: None,
505                stopping_action: None,
506                speed_sp: None,
507                time_sp: None,
508                position_sp: None,
509                spec: spec.clone(),
510            }
511            .into(),
512        );
513    }
514
515    Ok(motors)
516}
517
518fn discover_sensors() -> Result<HashMap<String, LegoSensor>> {
519    let mut sensors = HashMap::default();
520
521    for color_sensor in Ev3ColorSensor::list().context("Find color sensors")? {
522        let address = color_sensor
523            .get_address()?
524            .strip_prefix("ev3-ports:")
525            .context("Strip prefix")?
526            .to_owned();
527        sensors.insert(
528            address,
529            LegoSensor::Color(
530                init_color_sensor(color_sensor)
531                    .context("Init color sensor")?
532                    .into(),
533            ),
534        );
535    }
536    for gyro_sensor in Ev3GyroSensor::list().context("Find gyro sensors")? {
537        let address = gyro_sensor
538            .get_address()?
539            .strip_prefix("ev3-ports:")
540            .context("Strip prefix")?
541            .to_owned();
542        sensors.insert(
543            address,
544            LegoSensor::Gyro(
545                init_gyro_sensor(gyro_sensor)
546                    .context("Init gyro sensor")?
547                    .into(),
548            ),
549        );
550    }
551
552    Ok(sensors)
553}
554
555fn init_color_sensor(color: Ev3ColorSensor) -> Result<LegoColorSensor> {
556    color
557        .set_mode_col_reflect()
558        .context("Set color sensor mode")?;
559
560    Ok(LegoColorSensor {
561        color,
562        white: 1.0,
563        black: 0.0,
564    })
565}
566
567fn init_gyro_sensor(gyro: Ev3GyroSensor) -> Result<LegoGyroSensor> {
568    gyro.set_mode_gyro_ang().context("Set gyro sensor mode")?;
569
570    let mut gyro = LegoGyroSensor { gyro };
571
572    reset_gyro_hard(&mut gyro).context("Reset gyro hard")?;
573    reset_gyro_soft(&mut gyro).context("Reset gyro soft")?;
574
575    Ok(gyro)
576}
577
578fn reset_gyro_hard(gyro: &mut LegoGyroSensor) -> Result<()> {
579    let address = gyro.gyro.get_address().context("Get sensor address")?;
580
581    let mode = Attribute::from_path_with_discriminator(
582        "/sys/class/lego-port",
583        "/mode",
584        "/address",
585        address.as_str(),
586    )
587    .context("Get mode attribute")?;
588
589    mode.set_str_slice("other-uart")
590        .context("Hard reset gyro")?;
591    thread::sleep(Duration::from_millis(300));
592    mode.set_str_slice("auto").context("Hard reset gyro")?;
593
594    for _ in 0..100 {
595        let gyros = Ev3GyroSensor::list().context("Find gyro sensors")?;
596        for new_gyro in gyros {
597            if new_gyro
598                .get_address()
599                .context("Get new sensor address")?
600                .contains(&address)
601            {
602                gyro.gyro = new_gyro;
603
604                // Wait for driver to initalize
605                thread::sleep(Duration::from_millis(500));
606
607                return Ok(());
608            }
609        }
610
611        // Busy wait the gyro to come back online
612        thread::sleep(Duration::from_millis(100));
613    }
614
615    bail!("Gyro did not reconnect within 10s");
616}
617
618fn reset_gyro_soft(gyro: &mut LegoGyroSensor) -> Result<()> {
619    gyro.gyro
620        .get_attribute("direct")
621        .set_str_slice("\x11")
622        .context("Write reset command")?;
623
624    thread::sleep(Duration::from_millis(25));
625
626    Ok(())
627}