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 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 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 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 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((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 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 thread::sleep(Duration::from_millis(500));
606
607 return Ok(());
608 }
609 }
610
611 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}