lss_driver/lib.rs
1#![doc = include_str!("../README.md")]
2
3mod message_types;
4mod serial_driver;
5
6pub use message_types::*;
7use serial_driver::{FramedDriver, FramedSerialDriver, LssCommand};
8use std::str;
9
10/// ID used to talk to all motors on a bus at once
11pub const BROADCAST_ID: u8 = 254;
12
13type DriverResult<T> = Result<T, LssDriverError>;
14
15/// Driver for the LSS servo
16pub struct LSSDriver {
17 driver: Box<dyn FramedDriver + Send + Sync>,
18}
19
20impl LSSDriver {
21 /// Create new driver on a serial port with default settings
22 ///
23 /// Default baud_rate is 115200
24 ///
25 /// # Arguments
26 ///
27 /// * `post` - Port to use. e.g. COM1 or /dev/ttyACM0
28 ///
29 /// # Example
30 ///
31 /// ```no_run
32 /// use lss_driver::LSSDriver;
33 /// let mut driver = LSSDriver::new("COM1").unwrap();
34 /// ```
35 pub fn new(port: &str) -> DriverResult<LSSDriver> {
36 let driver = FramedSerialDriver::new(port)?;
37 Ok(LSSDriver {
38 driver: Box::new(driver),
39 })
40 }
41
42 /// Create new driver on a serial port with custom baud rate
43 ///
44 /// # Arguments
45 ///
46 /// * `post` - Port to use. e.g. COM1 or /dev/ttyACM0
47 /// * `baud_rate` - Baudrate. e.g. 115200
48 ///
49 /// # Example
50 ///
51 /// ```no_run
52 /// use lss_driver::LSSDriver;
53 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
54 /// ```
55 pub fn with_baud_rate(port: &str, baud_rate: u32) -> DriverResult<LSSDriver> {
56 let driver = FramedSerialDriver::with_baud_rate(port, baud_rate)?;
57 Ok(LSSDriver {
58 driver: Box::new(driver),
59 })
60 }
61
62 /// Creates new LSS driver with a custom implementation of the transport
63 ///
64 /// This is used for tests and can be used if you want to reimplement the driver over network
65 pub fn with_driver(driver: Box<dyn FramedDriver + Send + Sync>) -> LSSDriver {
66 LSSDriver { driver }
67 }
68
69 /// Soft reset
70 /// This command does a "soft reset" and reverts all commands to those stored in EEPROM
71 ///
72 /// [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HReset)
73 ///
74 /// # Arguments
75 ///
76 /// * `id` - ID of servo you want to reset
77 pub async fn reset(&mut self, id: u8) -> DriverResult<()> {
78 self.driver.send(LssCommand::simple(id, "RESET")).await?;
79 Ok(())
80 }
81
82 /// Query value of ID
83 /// Especially useful with BROADCAST_ID
84 ///
85 /// [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HIdentificationNumber28ID29)
86 ///
87 /// # Arguments
88 ///
89 /// * `id` - ID of servo you want to control
90 ///
91 /// # Example
92 ///
93 /// ```no_run
94 /// use lss_driver::LSSDriver;
95 /// async fn async_main(){
96 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
97 /// let id = driver.query_id(lss_driver::BROADCAST_ID).await.unwrap();
98 /// }
99 /// ```
100 pub async fn query_id(&mut self, id: u8) -> DriverResult<u8> {
101 self.driver.send(LssCommand::simple(id, "QID")).await?;
102 let response = self.driver.receive().await?;
103 let value = response.get_val("QID")?;
104 Ok(value as u8)
105 }
106
107 /// Set value of ID
108 /// Saved to EEPROM
109 /// Only takes effect after restart
110 ///
111 /// [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HIdentificationNumber28ID29)
112 ///
113 /// # Arguments
114 ///
115 /// * `id` - ID of servo you want to control
116 /// * `new_id` - ID You want that servo to have
117 pub async fn set_id(&mut self, id: u8, new_id: u8) -> DriverResult<()> {
118 self.driver
119 .send(LssCommand::with_param(id, "CID", new_id as i32))
120 .await?;
121 Ok(())
122 }
123
124 /// set color for driver with id
125 ///
126 /// # Arguments
127 ///
128 /// * `id` - ID of servo you want to control
129 /// * `color` - Color to set
130 pub async fn set_color(&mut self, id: u8, color: LedColor) -> DriverResult<()> {
131 self.driver
132 .send(LssCommand::with_param(id, "LED", color as i32))
133 .await?;
134 Ok(())
135 }
136
137 /// configure color for motor with id (value will be saved)
138 ///
139 /// # Arguments
140 ///
141 /// * `id` - ID of servo you want to control
142 /// * `color` - Color to set
143 pub async fn configure_color(&mut self, id: u8, color: LedColor) -> DriverResult<()> {
144 self.driver
145 .send(LssCommand::with_param(id, "CLED", color as i32))
146 .await?;
147 Ok(())
148 }
149
150 /// Query color of servo LED
151 ///
152 /// # Arguments
153 ///
154 /// * `id` - ID of servo you want to query
155 pub async fn query_color(&mut self, id: u8) -> DriverResult<LedColor> {
156 self.driver.send(LssCommand::simple(id, "QLED")).await?;
157 let response = self.driver.receive().await?;
158 let (_, value) = response.separate("QLED")?;
159 LedColor::from_i32(value)
160 }
161
162 /// Move to absolute position in degrees
163 ///
164 /// Supports virtual positions that are more than 360 degrees
165 ///
166 /// # Arguments
167 ///
168 /// * `id` - ID of servo you want to control
169 /// * `position` - Absolute position in degrees
170 ///
171 /// # Example
172 ///
173 /// ```no_run
174 /// use lss_driver::LSSDriver;
175 /// async fn async_main(){
176 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
177 /// driver.move_to_position(5, 180.0).await;
178 /// driver.move_to_position(5, 480.0).await;
179 /// }
180 /// ```
181 pub async fn move_to_position(&mut self, id: u8, position: f32) -> DriverResult<()> {
182 let angle = (position * 10.0).round() as i32;
183 self.driver
184 .send(LssCommand::with_param(id, "D", angle))
185 .await?;
186 Ok(())
187 }
188
189 /// Move to absolute position in degrees with modifier
190 ///
191 /// Supports virtual positions that are more than 360 degrees
192 ///
193 /// # Arguments
194 ///
195 /// * `id` - ID of servo you want to control
196 /// * `position` - Absolute position in degrees
197 /// * `modifier` - Modifier applied to this motion. Look at the type for more info.
198 ///
199 /// # Example
200 ///
201 /// ```no_run
202 /// use lss_driver::LSSDriver;
203 /// use lss_driver::CommandModifier;
204 /// async fn async_main(){
205 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
206 /// driver.move_to_position_with_modifier(5, 180.0, CommandModifier::Timed(2500)).await;
207 /// driver.move_to_position_with_modifier(5, 480.0, CommandModifier::Timed(2500)).await;
208 /// }
209 /// ```
210 pub async fn move_to_position_with_modifier(
211 &mut self,
212 id: u8,
213 position: f32,
214 modifier: CommandModifier,
215 ) -> DriverResult<()> {
216 let angle = (position * 10.0).round() as i32;
217 self.driver
218 .send(LssCommand::with_param_modifier(id, "D", angle, modifier))
219 .await?;
220 Ok(())
221 }
222
223 /// Move to absolute position in degrees with multiple modifiers
224 ///
225 /// Supports virtual positions that are more than 360 degrees
226 /// Be careful about which modifiers are supported together
227 ///
228 /// # Arguments
229 ///
230 /// * `id` - ID of servo you want to control
231 /// * `position` - Absolute position in degrees
232 /// * `modifiers` - Array of modifiers applied to this motion. Look at the type for more info.
233 ///
234 /// # Example
235 ///
236 /// ```no_run
237 /// use lss_driver::LSSDriver;
238 /// use lss_driver::CommandModifier;
239 /// async fn async_main(){
240 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
241 /// driver.move_to_position_with_modifiers(5, 180.0, &[CommandModifier::Timed(2500), CommandModifier::CurrentHold(400)]).await;
242 /// }
243 /// ```
244 pub async fn move_to_position_with_modifiers(
245 &mut self,
246 id: u8,
247 position: f32,
248 modifiers: &[CommandModifier],
249 ) -> DriverResult<()> {
250 let angle = (position * 10.0).round() as i32;
251 self.driver
252 .send(LssCommand::with_param_modifiers(id, "D", angle, modifiers))
253 .await?;
254 Ok(())
255 }
256
257 /// Move to absolute position in degrees
258 ///
259 /// Same as `move_to_position`
260 ///
261 /// # Arguments
262 ///
263 /// * `id` - ID of servo you want to control
264 /// * `position` - Absolute position in degrees
265 ///
266 /// # Example
267 ///
268 /// ```no_run
269 /// use lss_driver::LSSDriver;
270 /// async fn async_main(){
271 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
272 /// driver.set_target_position(5, 180.0).await;
273 /// driver.set_target_position(5, 480.0).await;
274 /// }
275 /// ```
276 pub async fn set_target_position(&mut self, id: u8, position: f32) -> DriverResult<()> {
277 self.move_to_position(id, position).await
278 }
279
280 /// Query absolute current position in degrees
281 ///
282 /// Supports virtual positions that are more than 360 degrees
283 ///
284 /// # Arguments
285 ///
286 /// * `id` - ID of servo you want to query
287 pub async fn query_position(&mut self, id: u8) -> DriverResult<f32> {
288 self.driver.send(LssCommand::simple(id, "QD")).await?;
289 let response = self.driver.receive().await?;
290 let (_, value) = response.separate("QD")?;
291 Ok(value as f32 / 10.0)
292 }
293
294 /// Query absolute target position in degrees
295 ///
296 /// Supports virtual positions that are more than 360 degrees
297 ///
298 /// # Arguments
299 ///
300 /// * `id` - ID of servo you want to query
301 pub async fn query_target_position(&mut self, id: u8) -> DriverResult<f32> {
302 self.driver.send(LssCommand::simple(id, "QDT")).await?;
303 let response = self.driver.receive().await?;
304 let (_, value) = response.separate("QDT")?;
305 Ok(value as f32 / 10.0)
306 }
307
308 /// Set continuous rotation speed in °/s
309 ///
310 /// # Arguments
311 ///
312 /// * `id` - ID of servo you want to control
313 /// * `speed` - Speed in °/s
314 pub async fn set_rotation_speed(&mut self, id: u8, speed: f32) -> DriverResult<()> {
315 self.driver
316 .send(LssCommand::with_param(id, "WD", speed as i32))
317 .await?;
318 Ok(())
319 }
320
321 /// Set continuous rotation speed in °/s with modifier
322 ///
323 /// # Arguments
324 ///
325 /// * `id` - ID of servo you want to control
326 /// * `speed` - Speed in °/s
327 /// * `modifier` - Modifier applied to this motion. Look at the type for more info.
328 pub async fn set_rotation_speed_with_modifier(
329 &mut self,
330 id: u8,
331 speed: f32,
332 modifier: CommandModifier,
333 ) -> DriverResult<()> {
334 self.driver
335 .send(LssCommand::with_param_modifier(
336 id,
337 "WD",
338 speed as i32,
339 modifier,
340 ))
341 .await?;
342 Ok(())
343 }
344
345 /// Query absolute rotation speed in °/s
346 ///
347 /// # Arguments
348 ///
349 /// * `id` - ID of servo you want to query
350 pub async fn query_rotation_speed(&mut self, id: u8) -> DriverResult<f32> {
351 self.driver.send(LssCommand::simple(id, "QWD")).await?;
352 let response = self.driver.receive().await?;
353 let (_, value) = response.separate("QWD")?;
354 Ok(value as f32)
355 }
356
357 /// Query status of a motor
358 ///
359 /// View more on [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HQueryStatus28Q29)
360 ///
361 /// # Arguments
362 ///
363 /// * `id` - ID of servo you want to query
364 pub async fn query_status(&mut self, id: u8) -> DriverResult<MotorStatus> {
365 self.driver.send(LssCommand::simple(id, "Q")).await?;
366 let response = self.driver.receive().await?;
367 let (_, value) = response.separate("Q")?;
368 MotorStatus::from_i32(value)
369 }
370
371 /// Query safety status of a motor
372 ///
373 /// View more on [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HQueryStatus28Q29)
374 ///
375 /// # Arguments
376 ///
377 /// * `id` - ID of servo you want to query
378 pub async fn query_safety_status(&mut self, id: u8) -> DriverResult<SafeModeStatus> {
379 self.driver.send(LssCommand::simple(id, "Q1")).await?;
380 let response = self.driver.receive().await?;
381 let (_, value) = response.separate("Q")?;
382 SafeModeStatus::from_i32(value)
383 }
384
385 /// Set motion profile enabled or disabled.
386 /// If the motion profile is enabled, angular acceleration (AA) and angular deceleration(AD) will have an effect on the motion. Also, SD/S and T modifiers can be used.
387 ///
388 /// With motion profile enabled servos will follow a motion curve
389 /// With motion profile disabled servos move towards target location at full speed
390 ///
391 /// # Arguments
392 ///
393 /// * `id` - ID of servo you want to control
394 /// * `motion_profile` - set motion profile on/off
395 pub async fn set_motion_profile(&mut self, id: u8, motion_profile: bool) -> DriverResult<()> {
396 self.driver
397 .send(LssCommand::with_param(id, "EM", motion_profile as i32))
398 .await?;
399 Ok(())
400 }
401
402 /// query motion profile enabled or disabled.
403 /// If the motion profile is enabled, angular acceleration (AA) and angular deceleration(AD) will have an effect on the motion. Also, SD/S and T modifiers can be used.
404 ///
405 /// With motion profile enabled servos will follow a motion curve
406 /// With motion profile disabled servos move towards target location at full speed
407 ///
408 /// # Arguments
409 ///
410 /// * `id` - ID of servo you want to query
411 pub async fn query_motion_profile(&mut self, id: u8) -> DriverResult<bool> {
412 self.driver.send(LssCommand::simple(id, "QEM")).await?;
413 let response = self.driver.receive().await?;
414 let (_, value) = response.separate("QEM")?;
415 Ok(value != 0)
416 }
417
418 /// Set filter position count
419 ///
420 /// Change the Filter Position Count value for this session.
421 /// Affects motion only when motion profile is disabled (EM0)
422 ///
423 /// more info at the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HFilterPositionCount28FPC29)
424 ///
425 /// # Arguments
426 ///
427 /// * `id` - ID of servo you want to control
428 /// * `filter_position_count` - default if 5
429 pub async fn set_filter_position_count(
430 &mut self,
431 id: u8,
432 filter_position_count: u8,
433 ) -> DriverResult<()> {
434 self.driver
435 .send(LssCommand::with_param(
436 id,
437 "FPC",
438 filter_position_count as i32,
439 ))
440 .await?;
441 Ok(())
442 }
443
444 /// Query filter position count
445 ///
446 /// Query the Filter Position Count value.
447 /// Affects motion only when motion profile is disabled (EM0)
448 ///
449 /// more info at the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HFilterPositionCount28FPC29)
450 ///
451 /// # Arguments
452 ///
453 /// * `id` - ID of servo you want to query
454 pub async fn query_filter_position_count(&mut self, id: u8) -> DriverResult<u8> {
455 self.driver.send(LssCommand::simple(id, "QFPC")).await?;
456 let response = self.driver.receive().await?;
457 let (_, value) = response.separate("QFPC")?;
458 Ok(value as u8)
459 }
460
461 /// Set angular stiffness
462 ///
463 /// Read more about [Angular stiffness](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularStiffness28AS29)
464 ///
465 /// # Arguments
466 ///
467 /// * `id` - ID of servo you want to control
468 /// * `angular_stiffness` - value for angular stiffness (-10 to 10) (recommended -4 to 4)
469 pub async fn set_angular_stiffness(
470 &mut self,
471 id: u8,
472 angular_stiffness: i32,
473 ) -> DriverResult<()> {
474 self.driver
475 .send(LssCommand::with_param(id, "AS", angular_stiffness))
476 .await?;
477 Ok(())
478 }
479
480 /// Query angular stiffness
481 ///
482 /// Read more about [Angular stiffness](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularStiffness28AS29)
483 ///
484 /// # Arguments
485 ///
486 /// * `id` - ID of servo you want to query
487 pub async fn query_angular_stiffness(&mut self, id: u8) -> DriverResult<i32> {
488 self.driver.send(LssCommand::simple(id, "QAS")).await?;
489 let response = self.driver.receive().await?;
490 let (_, value) = response.separate("QAS")?;
491 Ok(value)
492 }
493
494 /// Set angular holding stiffness
495 ///
496 /// Read more about [Angular holding stiffness](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularHoldingStiffness28AH29)
497 ///
498 /// # Arguments
499 ///
500 /// * `id` - ID of servo you want to control
501 /// * `angular_holding` - value for angular holding stiffness (-10 to 10)
502 pub async fn set_angular_holding_stiffness(
503 &mut self,
504 id: u8,
505 angular_holding: i32,
506 ) -> DriverResult<()> {
507 self.driver
508 .send(LssCommand::with_param(id, "AH", angular_holding))
509 .await?;
510 Ok(())
511 }
512
513 /// Query angular holding stiffness
514 ///
515 /// Read more about [Angular holding stiffness](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularHoldingStiffness28AH29)
516 ///
517 /// # Arguments
518 ///
519 /// * `id` - ID of servo you want to control
520 pub async fn query_angular_holding_stiffness(&mut self, id: u8) -> DriverResult<i32> {
521 self.driver.send(LssCommand::simple(id, "QAH")).await?;
522 let response = self.driver.receive().await?;
523 let (_, value) = response.separate("QAH")?;
524 Ok(value)
525 }
526
527 /// Set angular acceleration in degrees per second squared (°/s2)
528 ///
529 /// Accepts values between 1 and 100. Increments of 10
530 /// Only used when motion profile is enabled
531 ///
532 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularAcceleration28AA29)
533 ///
534 /// # Arguments
535 ///
536 /// * `id` - ID of servo you want to control
537 /// * `angular_acceleration` - value for angular acceleration (1 to 100, Increments 10)
538 pub async fn set_angular_acceleration(
539 &mut self,
540 id: u8,
541 angular_acceleration: i32,
542 ) -> DriverResult<()> {
543 self.driver
544 .send(LssCommand::with_param(id, "AA", angular_acceleration))
545 .await?;
546 Ok(())
547 }
548
549 /// Query angular acceleration in degrees per second squared (°/s2)
550 ///
551 /// Accepts values between 1 and 100. Increments of 10
552 /// Only used when motion profile is enabled
553 ///
554 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularAcceleration28AA29)
555 ///
556 /// # Arguments
557 ///
558 /// * `id` - ID of servo you want to query
559 pub async fn query_angular_acceleration(&mut self, id: u8) -> DriverResult<i32> {
560 self.driver.send(LssCommand::simple(id, "QAA")).await?;
561 let response = self.driver.receive().await?;
562 let (_, value) = response.separate("QAA")?;
563 Ok(value)
564 }
565
566 /// Set angular deceleration in degrees per second squared (°/s2)
567 ///
568 /// Accepts values between 1 and 100. Increments of 10
569 /// Only used when motion profile is enabled
570 ///
571 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularDeceleration28AD29)
572 ///
573 /// # Arguments
574 ///
575 /// * `id` - ID of servo you want to control
576 /// * `angular_deceleration` - value for angular deceleration (1 to 100, Increments 10)
577 pub async fn set_angular_deceleration(
578 &mut self,
579 id: u8,
580 angular_deceleration: i32,
581 ) -> DriverResult<()> {
582 self.driver
583 .send(LssCommand::with_param(id, "AD", angular_deceleration))
584 .await?;
585 Ok(())
586 }
587
588 /// Query angular deceleration in degrees per second squared (°/s2)
589 ///
590 /// Accepts values between 1 and 100. Increments of 10
591 /// Only used when motion profile is enabled
592 ///
593 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularDeceleration28AD29)
594 ///
595 /// # Arguments
596 ///
597 /// * `id` - ID of servo you want to query
598 pub async fn query_angular_deceleration(&mut self, id: u8) -> DriverResult<i32> {
599 self.driver.send(LssCommand::simple(id, "QAD")).await?;
600 let response = self.driver.receive().await?;
601 let (_, value) = response.separate("QAD")?;
602 Ok(value)
603 }
604
605 /// Set maximum motor duty
606 ///
607 /// Accepts values between 255 and 1023
608 /// Only used when motion profile is disabled
609 ///
610 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HMaximumMotorDuty28MMD29)
611 ///
612 /// # Arguments
613 ///
614 /// * `id` - ID of servo you want to control
615 /// * `maximum_motor_duty` - value for maximum motor duty (255 to 1023)
616 pub async fn set_maximum_motor_duty(
617 &mut self,
618 id: u8,
619 maximum_motor_duty: i32,
620 ) -> DriverResult<()> {
621 self.driver
622 .send(LssCommand::with_param(id, "MMD", maximum_motor_duty))
623 .await?;
624 Ok(())
625 }
626
627 /// Query maximum motor duty
628 ///
629 /// Accepts values between 255 and 1023
630 /// Only used when motion profile is disabled
631 ///
632 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HMaximumMotorDuty28MMD29)
633 ///
634 /// # Arguments
635 ///
636 /// * `id` - ID of servo you want to query
637 pub async fn query_maximum_motor_duty(&mut self, id: u8) -> DriverResult<i32> {
638 self.driver.send(LssCommand::simple(id, "QMMD")).await?;
639 let response = self.driver.receive().await?;
640 let (_, value) = response.separate("QMMD")?;
641 Ok(value)
642 }
643
644 /// Set maximum speed in degrees per second
645 ///
646 /// Accepts values up to 180.0
647 ///
648 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HMaximumSpeedinDegrees28SD29)
649 ///
650 /// # Arguments
651 ///
652 /// * `id` - ID of servo you want to control
653 /// * `maximum_speed` - value for maximum speed
654 pub async fn set_maximum_speed(&mut self, id: u8, maximum_speed: f32) -> DriverResult<()> {
655 self.driver
656 .send(LssCommand::with_param(
657 id,
658 "SD",
659 (maximum_speed * 10.) as i32,
660 ))
661 .await?;
662 Ok(())
663 }
664
665 /// Query maximum speed in degrees per second
666 ///
667 /// Accepts values up to 180.0
668 ///
669 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HMaximumSpeedinDegrees28SD29)
670 ///
671 /// # Arguments
672 ///
673 /// * `id` - ID of servo you want to query
674 pub async fn query_maximum_speed(&mut self, id: u8) -> DriverResult<f32> {
675 self.driver.send(LssCommand::simple(id, "QSD")).await?;
676 let response = self.driver.receive().await?;
677 let (_, value) = response.separate("QSD")?;
678 Ok(value as f32 / 10.)
679 }
680
681 /// Disables power to motor allowing it to be back driven
682 ///
683 /// # Arguments
684 ///
685 /// * `id` - ID of servo you want to control
686 pub async fn limp(&mut self, id: u8) -> DriverResult<()> {
687 self.driver.send(LssCommand::simple(id, "L")).await?;
688 Ok(())
689 }
690
691 /// Stops any ongoing motor motion and actively holds position
692 ///
693 /// # Arguments
694 ///
695 /// * `id` - ID of servo you want to control
696 pub async fn halt_hold(&mut self, id: u8) -> DriverResult<()> {
697 self.driver.send(LssCommand::simple(id, "H")).await?;
698 Ok(())
699 }
700
701 /// Query voltage of motor in volts
702 ///
703 /// # Arguments
704 ///
705 /// * `id` - ID of servo you want to Query
706 pub async fn query_voltage(&mut self, id: u8) -> DriverResult<f32> {
707 // response message looks like *5QV11200<cr>
708 // Response is in mV
709 self.driver.send(LssCommand::simple(id, "QV")).await?;
710 let response = self.driver.receive().await?;
711 let (_, value) = response.separate("QV")?;
712 Ok(value as f32 / 1000.0)
713 }
714
715 /// Query temperature of motor in celsius
716 ///
717 /// # Arguments
718 ///
719 /// * `id` - ID of servo you want to Query
720 pub async fn query_temperature(&mut self, id: u8) -> DriverResult<f32> {
721 // response message looks like *5QT441<cr>
722 // Response is in 10s of celsius
723 // 441 would be 44.1 celsius
724 self.driver.send(LssCommand::simple(id, "QT")).await?;
725 let response = self.driver.receive().await?;
726 let (_, value) = response.separate("QT")?;
727 Ok(value as f32 / 10.0)
728 }
729
730 /// Query current of motor in Amps
731 ///
732 /// # Arguments
733 ///
734 /// * `id` - ID of servo you want to Query
735 pub async fn query_current(&mut self, id: u8) -> DriverResult<f32> {
736 // response message looks like *5QT441<cr>
737 // Response is in mA
738 self.driver.send(LssCommand::simple(id, "QC")).await?;
739 let response = self.driver.receive().await?;
740 let (_, value) = response.separate("QC")?;
741 Ok(value as f32 / 1000.0)
742 }
743
744 /// Query model string
745 ///
746 /// # Arguments
747 ///
748 /// * `id` - ID of servo you want to query
749 pub async fn query_model(&mut self, id: u8) -> DriverResult<Model> {
750 self.driver.send(LssCommand::simple(id, "QMS")).await?;
751 let response = self.driver.receive().await?;
752 let (_, value) = response.separate_string("QMS")?;
753 Ok(Model::from_str(&value))
754 }
755
756 /// Query firmware version
757 ///
758 /// # Arguments
759 ///
760 /// * `id` - ID of servo you want to query
761 pub async fn query_firmware_version(&mut self, id: u8) -> DriverResult<String> {
762 self.driver.send(LssCommand::simple(id, "QF")).await?;
763 let response = self.driver.receive().await?;
764 let (_, value) = response.separate_string("QF")?;
765 Ok(value)
766 }
767
768 /// Query serial number
769 ///
770 /// # Arguments
771 ///
772 /// * `id` - ID of servo you want to query
773 pub async fn query_serial_number(&mut self, id: u8) -> DriverResult<String> {
774 self.driver.send(LssCommand::simple(id, "QN")).await?;
775 let response = self.driver.receive().await?;
776 let (_, value) = response.separate_string("QN")?;
777 Ok(value)
778 }
779
780 /// Set LED blinking mode
781 ///
782 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HConfigureLEDBlinking28CLB29)
783 ///
784 /// # Arguments
785 ///
786 /// * `id` - ID of servo you want to control
787 /// * `blinking_mode` - Blinking mode desired. Can be combination to make motor blink during multiple modes
788 pub async fn set_led_blinking(
789 &mut self,
790 id: u8,
791 blinking_mode: Vec<LedBlinking>,
792 ) -> DriverResult<()> {
793 let sum = blinking_mode
794 .iter()
795 .map(|item| *item as i32)
796 .sum::<i32>()
797 .min(LedBlinking::AlwaysBlink as i32);
798 self.driver
799 .send(LssCommand::with_param(id, "CLB", sum))
800 .await?;
801 Ok(())
802 }
803
804 /// Query origin offset in degrees
805 ///
806 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HOriginOffset28O29)
807 ///
808 /// # Arguments
809 ///
810 /// * `id` - ID of servo you want to query
811 ///
812 /// # Example
813 /// ```no_run
814 /// use lss_driver::LSSDriver;
815 ///
816 /// async fn async_main() {
817 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
818 /// let origin_offset = driver.query_origin_offset(5).await;
819 /// }
820 /// ```
821 pub async fn query_origin_offset(&mut self, id: u8) -> DriverResult<f32> {
822 // response messages looks like *5QO-13
823 // Response is in tenths of degrees
824 self.driver.send(LssCommand::simple(id, "QO")).await?;
825 let response = self.driver.receive().await?;
826 let (_, value) = response.separate("QO")?;
827 Ok(value as f32 / 10.0)
828 }
829
830 /// Query the angular range in degrees
831 ///
832 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularRange28AR29)
833 ///
834 /// # Arguments
835 ///
836 /// * `id` - ID of the servo you want to query
837 ///
838 /// /// # Example
839 /// ```no_run
840 /// use lss_driver::LSSDriver;
841 ///
842 /// async fn async_main() {
843 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
844 /// let angular_range = driver.query_angular_range(5).await.unwrap();
845 /// }
846 /// ```
847 pub async fn query_angular_range(&mut self, id: u8) -> DriverResult<f32> {
848 // Response looks like *5QAR1800 where 1800 is range in 1/10 degrees
849 // Contrary what the wiki says, which is *5AR1800 as example, the servo I used (HT1) returns
850 // *5QAR1800
851 self.driver.send(LssCommand::simple(id, "QAR")).await?;
852
853 let response = self.driver.receive().await?;
854 let (_, value) = response.separate("QAR")?;
855
856 Ok(value as f32 / 10.0)
857 }
858
859 /// Set the angular range in degrees
860 ///
861 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HAngularRange28AR29)
862 ///
863 /// # Arguments
864 ///
865 /// * `id` - ID of the servo you want to control
866 /// * `range` - Angular range in degrees
867 ///
868 /// # Example
869 /// ```no_run
870 /// use lss_driver::LSSDriver;
871 ///
872 /// async fn async_main() {
873 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
874 /// driver.set_angular_range(5, 180.0).await;
875 /// }
876 pub async fn set_angular_range(&mut self, id: u8, range: f32) -> DriverResult<()> {
877 self.driver
878 .send(LssCommand::with_param(id, "CAR", (range * 10.) as i32))
879 .await?;
880
881 Ok(())
882 }
883
884 /// Queries the position in µs.
885 ///
886 /// When the position is inside the angular range it will return a value between 500 and 2500,
887 /// when the position is outside the angular range it will return either -500 or -2500.
888 ///
889 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HPositioninPWM28P29)
890 ///
891 /// # Arguments
892 ///
893 /// * `id` - ID of the servo you want to control
894 ///
895 /// # Example
896 /// ```no_run
897 /// use lss_driver::LSSDriver;
898 ///
899 /// async fn async_main() {
900 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
901 /// let pwm_position = driver.query_pwm_position(5).await.unwrap();
902 /// }
903 /// ```
904 pub async fn query_pwm_position(&mut self, id: u8) -> DriverResult<i32> {
905 // Response looks like *5QP2334 where 2335 is in µs
906 self.driver.send(LssCommand::simple(id, "QP")).await?;
907
908 let response = self.driver.receive().await?;
909 let (_, value) = response.separate("QP")?;
910
911 Ok(value)
912 }
913
914 /// Set origin offset in degrees
915 ///
916 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HOriginOffset28O29)
917 ///
918 /// # Arguments
919 ///
920 /// * `id` - ID of servo you want to control
921 /// * `origin_offset` - Offset from factory 0 in degrees
922 ///
923 /// # Example
924 /// ```no_run
925 /// use lss_driver::LSSDriver;
926 ///
927 /// async fn async_main() {
928 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
929 /// driver.set_origin_offset(5, -1.3).await;
930 /// }
931 /// ```
932 pub async fn set_origin_offset(&mut self, id: u8, origin_offset: f32) -> DriverResult<()> {
933 self.driver
934 .send(LssCommand::with_param(
935 id,
936 "CO",
937 (origin_offset * 10.) as i32,
938 ))
939 .await?;
940 Ok(())
941 }
942
943 /// Move to PWM position in µs.
944 ///
945 /// You can use [set_angular_range](LSSDriver::set_angular_range) to range.
946 ///
947 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HPositioninPWM28P29)
948 ///
949 /// # Arguments
950 ///
951 /// * `id` - ID of the servo you want to control
952 /// * `position` - Position in µs in the range [500,2500]
953 ///
954 /// # Example
955 /// ```no_run
956 /// use lss_driver::LSSDriver;
957 ///
958 /// async fn async_main() {
959 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
960 /// driver.move_to_pwm_position(5, 2334).await;
961 /// }
962 /// ```
963 pub async fn move_to_pwm_position(&mut self, id: u8, position: i32) -> DriverResult<()> {
964 self.driver
965 .send(LssCommand::with_param(id, "P", position))
966 .await?;
967
968 Ok(())
969 }
970
971 /// Move to PWM position in µs with modifier.
972 ///
973 /// You can use [set_angular_range](LSSDriver::set_angular_range) to range.
974 ///
975 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HPositioninPWM28P29)
976 ///
977 /// # Arguments
978 ///
979 /// * `id` - ID of the servo you want to control
980 /// * `position` - Position in µs in the range [500,2500]
981 /// * `modifier` - Modifier applied to this motion. Look at the type for more info.
982 ///
983 /// # Example
984 /// ```no_run
985 /// use lss_driver::{LSSDriver, CommandModifier};
986 ///
987 /// async fn async_main() {
988 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
989 /// driver.move_to_pwm_position_with_modifier(5, 2334, CommandModifier::Speed(750)).await;
990 /// }
991 /// ```
992 pub async fn move_to_pwm_position_with_modifier(
993 &mut self,
994 id: u8,
995 position: i32,
996 modifier: CommandModifier,
997 ) -> DriverResult<()> {
998 self.driver
999 .send(LssCommand::with_param_modifier(id, "P", position, modifier))
1000 .await?;
1001
1002 Ok(())
1003 }
1004
1005 /// Move to PWM position in µs with modifiers.
1006 ///
1007 /// You can use [set_angular_range](LSSDriver::set_angular_range) to range.
1008 ///
1009 /// Read more on the [wiki](https://www.robotshop.com/info/wiki/lynxmotion/view/lynxmotion-smart-servo/lss-communication-protocol/#HPositioninPWM28P29)
1010 ///
1011 /// # Arguments
1012 ///
1013 /// * `id` - ID of the servo you want to control
1014 /// * `position` - Position in µs in the range [500,2500]
1015 /// * `modifiers` - Array of modifiers applied to this motion. Look at the type for more info.
1016 ///
1017 /// # Example
1018 /// ```no_run
1019 /// use lss_driver::{LSSDriver, CommandModifier};
1020 ///
1021 /// async fn async_main() {
1022 /// let mut driver = LSSDriver::with_baud_rate("COM1", 115200).unwrap();
1023 /// driver.move_to_pwm_position_with_modifiers(5, 2334, &[CommandModifier::Speed(750), CommandModifier::Timed(2500)]).await;
1024 /// }
1025 /// ```
1026 pub async fn move_to_pwm_position_with_modifiers(
1027 &mut self,
1028 id: u8,
1029 position: i32,
1030 modifiers: &[CommandModifier],
1031 ) -> DriverResult<()> {
1032 self.driver
1033 .send(LssCommand::with_param_modifiers(
1034 id, "P", position, modifiers,
1035 ))
1036 .await?;
1037
1038 Ok(())
1039 }
1040}
1041
1042#[cfg(test)]
1043mod tests {
1044 use super::serial_driver::LssResponse;
1045 use super::*;
1046 use approx::assert_relative_eq;
1047 use async_trait::async_trait;
1048
1049 struct MockedDriver {
1050 expected_send: Vec<String>,
1051 receive: Vec<String>,
1052 }
1053
1054 #[async_trait]
1055 impl FramedDriver for MockedDriver {
1056 async fn send(&mut self, command: LssCommand) -> DriverResult<()> {
1057 let expected = self.expected_send.pop().unwrap();
1058 assert_eq!(expected, command.as_str().to_owned());
1059 Ok(())
1060 }
1061
1062 async fn receive(&mut self) -> DriverResult<LssResponse> {
1063 Ok(LssResponse::new(self.receive.pop().unwrap()))
1064 }
1065 }
1066
1067 #[tokio::test]
1068 async fn async_test_builds() {}
1069
1070 #[tokio::test]
1071 async fn test_limp_color_move_hold() {
1072 let mocked_framed_driver = MockedDriver {
1073 expected_send: vec![
1074 "#5QV\r".to_owned(),
1075 "#4H\r".to_owned(),
1076 "#3D1800\r".to_owned(),
1077 "#2LED1\r".to_owned(),
1078 "#1L\r".to_owned(),
1079 ],
1080 receive: vec!["*5QV11200\r".to_owned()],
1081 };
1082 let mut driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
1083 driver.limp(1).await.unwrap();
1084 driver.set_color(2, LedColor::Red).await.unwrap();
1085 driver.move_to_position(3, 180.0).await.unwrap();
1086 driver.halt_hold(4).await.unwrap();
1087 let voltage = driver.query_voltage(5).await.unwrap();
1088 assert_relative_eq!(voltage, 11.2);
1089 }
1090
1091 macro_rules! test_command {
1092 ($name:ident, $expected:expr, $command:expr) => {
1093 #[tokio::test]
1094 #[allow(clippy::redundant_closure_call)]
1095 async fn $name() {
1096 let mocked_framed_driver = MockedDriver {
1097 expected_send: vec![$expected.to_owned()],
1098 receive: vec![],
1099 };
1100 let driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
1101 ($command)(driver).await;
1102 }
1103 };
1104 }
1105
1106 macro_rules! test_query {
1107 ($name:ident, $expected:expr, $recv:expr, $command:expr, $val:expr) => {
1108 #[tokio::test]
1109 #[allow(clippy::redundant_closure_call)]
1110 async fn $name() {
1111 let mocked_framed_driver = MockedDriver {
1112 expected_send: vec![$expected.to_owned()],
1113 receive: vec![$recv.to_owned()],
1114 };
1115 let driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
1116 let res = ($command)(driver).await;
1117 assert_eq!(res, $val);
1118 }
1119 };
1120 }
1121
1122 macro_rules! test_query_float {
1123 ($name:ident, $expected:expr, $recv:expr, $command:expr, $val:expr) => {
1124 #[tokio::test]
1125 #[allow(clippy::redundant_closure_call)]
1126 async fn $name() {
1127 let mocked_framed_driver = MockedDriver {
1128 expected_send: vec![$expected.to_owned()],
1129 receive: vec![$recv.to_owned()],
1130 };
1131 let driver = LSSDriver::with_driver(Box::new(mocked_framed_driver));
1132 let res = ($command)(driver).await;
1133 assert_relative_eq!(res, $val);
1134 }
1135 };
1136 }
1137
1138 test_command!(
1139 test_hold_command,
1140 "#4H\r",
1141 |mut driver: LSSDriver| async move {
1142 driver.halt_hold(4_u8).await.unwrap();
1143 }
1144 );
1145
1146 test_query!(
1147 test_query_id,
1148 "#254QID\r",
1149 "*QID5\r",
1150 |mut driver: LSSDriver| async move { driver.query_id(BROADCAST_ID).await.unwrap() },
1151 5
1152 );
1153 test_command!(
1154 test_set_id,
1155 "#1CID2\r",
1156 |mut driver: LSSDriver| async move { driver.set_id(1, 2).await.unwrap() }
1157 );
1158
1159 // Motion
1160 test_command!(
1161 test_move_to,
1162 "#1D200\r",
1163 |mut driver: LSSDriver| async move { driver.move_to_position(1, 20.0).await.unwrap() }
1164 );
1165 test_command!(
1166 test_move_to_with_modifier,
1167 "#1D200T15000\r",
1168 |mut driver: LSSDriver| async move {
1169 driver
1170 .move_to_position_with_modifier(1, 20.0, CommandModifier::Timed(15000))
1171 .await
1172 .unwrap()
1173 }
1174 );
1175 test_command!(
1176 test_move_to_with_modifiers,
1177 "#1D200T15000CL400\r",
1178 |mut driver: LSSDriver| async move {
1179 driver
1180 .move_to_position_with_modifiers(
1181 1,
1182 20.0,
1183 &[
1184 CommandModifier::Timed(15000),
1185 CommandModifier::CurrentLimp(400),
1186 ],
1187 )
1188 .await
1189 .unwrap()
1190 }
1191 );
1192 test_command!(
1193 test_set_target_position,
1194 "#1D200\r",
1195 |mut driver: LSSDriver| async move { driver.set_target_position(1, 20.0).await.unwrap() }
1196 );
1197 test_query_float!(
1198 test_query_current_position,
1199 "#5QD\r",
1200 "*5QD132\r",
1201 |mut driver: LSSDriver| async move { driver.query_position(5).await.unwrap() },
1202 13.2
1203 );
1204 test_query_float!(
1205 test_query_target_position,
1206 "#5QDT\r",
1207 "*5QDT6783\r",
1208 |mut driver: LSSDriver| async move { driver.query_target_position(5).await.unwrap() },
1209 678.3
1210 );
1211
1212 // Wheel mode
1213 test_command!(
1214 test_set_rotation_speed_degrees,
1215 "#5WD90\r",
1216 |mut driver: LSSDriver| async move { driver.set_rotation_speed(5, 90.0).await.unwrap() }
1217 );
1218 test_command!(
1219 test_set_rotation_speed_degrees_with_modifier,
1220 "#1WD90T15000\r",
1221 |mut driver: LSSDriver| async move {
1222 driver
1223 .set_rotation_speed_with_modifier(1, 90.0, CommandModifier::Timed(15000))
1224 .await
1225 .unwrap()
1226 }
1227 );
1228 test_query_float!(
1229 test_query_rotation_speed_degrees,
1230 "#5QWD\r",
1231 "*5QWD90\r",
1232 |mut driver: LSSDriver| async move { driver.query_rotation_speed(5).await.unwrap() },
1233 90.0
1234 );
1235
1236 // Status
1237 test_query!(
1238 test_unknown_status,
1239 "#5Q\r",
1240 "*5Q0\r",
1241 |mut driver: LSSDriver| async move { driver.query_status(5).await.unwrap() },
1242 MotorStatus::Unknown
1243 );
1244 test_query!(
1245 test_holding_status,
1246 "#5Q\r",
1247 "*5Q6\r",
1248 |mut driver: LSSDriver| async move { driver.query_status(5).await.unwrap() },
1249 MotorStatus::Holding
1250 );
1251 test_query!(
1252 test_safety_status,
1253 "#5Q1\r",
1254 "*5Q3\r",
1255 |mut driver: LSSDriver| async move { driver.query_safety_status(5).await.unwrap() },
1256 SafeModeStatus::TemperatureLimit
1257 );
1258
1259 test_command!(test_limp, "#5L\r", |mut driver: LSSDriver| async move {
1260 driver.limp(5).await.unwrap()
1261 });
1262 test_command!(
1263 test_halt_hold,
1264 "#5H\r",
1265 |mut driver: LSSDriver| async move { driver.halt_hold(5).await.unwrap() }
1266 );
1267
1268 // LED
1269 test_command!(
1270 test_set_led,
1271 "#5LED3\r",
1272 |mut driver: LSSDriver| async move { driver.set_color(5, LedColor::Blue).await.unwrap() }
1273 );
1274 test_command!(
1275 test_configure_led,
1276 "#5CLED3\r",
1277 |mut driver: LSSDriver| async move { driver.configure_color(5, LedColor::Blue).await.unwrap() }
1278 );
1279 test_query!(
1280 test_query_led,
1281 "#5QLED\r",
1282 "*5QLED5\r",
1283 |mut driver: LSSDriver| async move { driver.query_color(5).await.unwrap() },
1284 LedColor::Cyan
1285 );
1286
1287 // motion profile
1288 test_command!(
1289 test_motion_profile_on,
1290 "#5EM1\r",
1291 |mut driver: LSSDriver| async move { driver.set_motion_profile(5, true).await.unwrap() }
1292 );
1293 test_command!(
1294 test_motion_profile_off,
1295 "#5EM0\r",
1296 |mut driver: LSSDriver| async move { driver.set_motion_profile(5, false).await.unwrap() }
1297 );
1298 test_query!(
1299 test_query_motion_profile_on,
1300 "#5QEM\r",
1301 "*5QEM1\r",
1302 |mut driver: LSSDriver| async move { driver.query_motion_profile(5).await.unwrap() },
1303 true
1304 );
1305 test_query!(
1306 test_query_motion_profile_off,
1307 "#5QEM\r",
1308 "*5QEM0\r",
1309 |mut driver: LSSDriver| async move { driver.query_motion_profile(5).await.unwrap() },
1310 false
1311 );
1312
1313 test_command!(
1314 test_set_filter_position_count,
1315 "#5FPC10\r",
1316 |mut driver: LSSDriver| async move { driver.set_filter_position_count(5, 10).await.unwrap() }
1317 );
1318 test_query!(
1319 test_query_filter_position_count,
1320 "#5QFPC\r",
1321 "*5QFPC10\r",
1322 |mut driver: LSSDriver| async move { driver.query_filter_position_count(5).await.unwrap() },
1323 10
1324 );
1325
1326 test_command!(
1327 test_set_angular_stiffness,
1328 "#5AS-2\r",
1329 |mut driver: LSSDriver| async move { driver.set_angular_stiffness(5, -2).await.unwrap() }
1330 );
1331 test_query!(
1332 test_query_angular_stiffness,
1333 "#5QAS\r",
1334 "*5QAS-2\r",
1335 |mut driver: LSSDriver| async move { driver.query_angular_stiffness(5).await.unwrap() },
1336 -2
1337 );
1338
1339 test_command!(
1340 test_set_angular_holding_stiffness,
1341 "#5AH3\r",
1342 |mut driver: LSSDriver| async move { driver.set_angular_holding_stiffness(5, 3).await.unwrap() }
1343 );
1344 test_query!(
1345 test_query_angular_holding_stiffness,
1346 "#5QAH\r",
1347 "*5QAH3\r",
1348 |mut driver: LSSDriver| async move { driver.query_angular_holding_stiffness(5).await.unwrap() },
1349 3
1350 );
1351
1352 test_command!(
1353 test_set_angular_acceleration,
1354 "#5AA30\r",
1355 |mut driver: LSSDriver| async move { driver.set_angular_acceleration(5, 30).await.unwrap() }
1356 );
1357 test_query!(
1358 test_query_angular_acceleration,
1359 "#5QAA\r",
1360 "*5QAA30\r",
1361 |mut driver: LSSDriver| async move { driver.query_angular_acceleration(5).await.unwrap() },
1362 30
1363 );
1364
1365 test_command!(
1366 test_set_angular_deceleration,
1367 "#5AD30\r",
1368 |mut driver: LSSDriver| async move { driver.set_angular_deceleration(5, 30).await.unwrap() }
1369 );
1370 test_query!(
1371 test_query_angular_deceleration,
1372 "#5QAD\r",
1373 "*5QAD30\r",
1374 |mut driver: LSSDriver| async move { driver.query_angular_deceleration(5).await.unwrap() },
1375 30
1376 );
1377
1378 test_command!(
1379 test_maximum_motor_duty,
1380 "#5MMD512\r",
1381 |mut driver: LSSDriver| async move { driver.set_maximum_motor_duty(5, 512).await.unwrap() }
1382 );
1383 test_query!(
1384 test_query_maximum_motor_duty,
1385 "#5QMMD\r",
1386 "*5QMMD512\r",
1387 |mut driver: LSSDriver| async move { driver.query_maximum_motor_duty(5).await.unwrap() },
1388 512
1389 );
1390
1391 test_command!(
1392 test_maximum_speed,
1393 "#5SD1800\r",
1394 |mut driver: LSSDriver| async move { driver.set_maximum_speed(5, 180.).await.unwrap() }
1395 );
1396 test_query_float!(
1397 test_query_maximum_speed,
1398 "#5QSD\r",
1399 "*5QSD1800\r",
1400 |mut driver: LSSDriver| async move { driver.query_maximum_speed(5).await.unwrap() },
1401 180.
1402 );
1403
1404 // test telemetry queries
1405 test_query_float!(
1406 test_query_voltage,
1407 "#5QV\r",
1408 "*5QV11200\r",
1409 |mut driver: LSSDriver| async move { driver.query_voltage(5).await.unwrap() },
1410 11.2
1411 );
1412 test_query_float!(
1413 test_query_temperature,
1414 "#5QT\r",
1415 "*5QT564\r",
1416 |mut driver: LSSDriver| async move { driver.query_temperature(5).await.unwrap() },
1417 56.4
1418 );
1419 test_query_float!(
1420 test_query_current,
1421 "#5QC\r",
1422 "*5QC140\r",
1423 |mut driver: LSSDriver| async move { driver.query_current(5).await.unwrap() },
1424 0.14
1425 );
1426
1427 test_query!(
1428 test_query_model_string,
1429 "#5QMS\r",
1430 "*5QMSLSS-HS1\r",
1431 |mut driver: LSSDriver| async move { driver.query_model(5).await.unwrap() },
1432 Model::HS1
1433 );
1434 test_query!(
1435 test_query_firmware_version,
1436 "#5QF\r",
1437 "*5QF368\r",
1438 |mut driver: LSSDriver| async move { driver.query_firmware_version(5).await.unwrap() },
1439 "368".to_owned()
1440 );
1441 test_query!(
1442 test_query_serial_number,
1443 "#5QN\r",
1444 "*5QN12345678\r",
1445 |mut driver: LSSDriver| async move { driver.query_serial_number(5).await.unwrap() },
1446 "12345678".to_owned()
1447 );
1448
1449 // Test blinking modes
1450 test_command!(
1451 test_blinking_mode_1,
1452 "#5CLB0\r",
1453 |mut driver: LSSDriver| async move {
1454 driver
1455 .set_led_blinking(5, vec![LedBlinking::NoBlinking])
1456 .await
1457 .unwrap()
1458 }
1459 );
1460 test_command!(
1461 test_blinking_mode_2,
1462 "#5CLB1\r",
1463 |mut driver: LSSDriver| async move {
1464 driver
1465 .set_led_blinking(5, vec![LedBlinking::Limp])
1466 .await
1467 .unwrap()
1468 }
1469 );
1470 test_command!(
1471 test_blinking_mode_3,
1472 "#5CLB2\r",
1473 |mut driver: LSSDriver| async move {
1474 driver
1475 .set_led_blinking(5, vec![LedBlinking::Holding])
1476 .await
1477 .unwrap()
1478 }
1479 );
1480 test_command!(
1481 test_blinking_mode_4,
1482 "#5CLB12\r",
1483 |mut driver: LSSDriver| async move {
1484 driver
1485 .set_led_blinking(
1486 5,
1487 vec![LedBlinking::Accelerating, LedBlinking::Decelerating],
1488 )
1489 .await
1490 .unwrap()
1491 }
1492 );
1493 test_command!(
1494 test_blinking_mode_5,
1495 "#5CLB48\r",
1496 |mut driver: LSSDriver| async move {
1497 driver
1498 .set_led_blinking(5, vec![LedBlinking::Free, LedBlinking::Travelling])
1499 .await
1500 .unwrap()
1501 }
1502 );
1503 test_command!(
1504 test_blinking_mode_6,
1505 "#5CLB63\r",
1506 |mut driver: LSSDriver| async move {
1507 driver
1508 .set_led_blinking(5, vec![LedBlinking::AlwaysBlink])
1509 .await
1510 .unwrap()
1511 }
1512 );
1513
1514 test_command!(
1515 test_reset,
1516 "#254RESET\r",
1517 |mut driver: LSSDriver| async move { driver.reset(BROADCAST_ID).await.unwrap() }
1518 );
1519
1520 test_query_float!(
1521 test_query_origin_offset,
1522 "#5QO\r",
1523 "*5QO-13\r",
1524 |mut driver: LSSDriver| async move { driver.query_origin_offset(5).await.unwrap() },
1525 -1.3
1526 );
1527
1528 test_command!(
1529 test_set_origin_offset,
1530 "#5CO-24\r",
1531 |mut driver: LSSDriver| async move { driver.set_origin_offset(5, -2.4).await.unwrap() }
1532 );
1533
1534 test_query_float!(
1535 test_query_angular_range,
1536 "#5QAR\r",
1537 "*5QAR1800\r",
1538 |mut driver: LSSDriver| async move { driver.query_angular_range(5).await.unwrap() },
1539 180.0
1540 );
1541
1542 test_command!(
1543 test_set_angular_range,
1544 "#5CAR1800\r",
1545 |mut driver: LSSDriver| async move { driver.set_angular_range(5, 180.0).await.unwrap() }
1546 );
1547
1548 test_query!(
1549 test_query_pwm_position,
1550 "#5QP\r",
1551 "*5QP2334\r",
1552 |mut driver: LSSDriver| async move { driver.query_pwm_position(5).await.unwrap() },
1553 2334
1554 );
1555
1556 test_command!(
1557 test_move_to_pwm_position,
1558 "#5P2334\r",
1559 |mut driver: LSSDriver| async move { driver.move_to_pwm_position(5, 2334).await.unwrap() }
1560 );
1561
1562 test_command!(
1563 test_move_to_pwm_position_with_modifier,
1564 "#5P2334S750\r",
1565 |mut driver: LSSDriver| async move {
1566 driver
1567 .move_to_pwm_position_with_modifier(5, 2334, CommandModifier::Speed(750))
1568 .await
1569 .unwrap()
1570 }
1571 );
1572
1573 test_command!(
1574 test_move_to_pwm_position_with_modifiers,
1575 "#5P2334S750T2500\r",
1576 |mut driver: LSSDriver| async move {
1577 driver
1578 .move_to_pwm_position_with_modifiers(
1579 5,
1580 2334,
1581 &[CommandModifier::Speed(750), CommandModifier::Timed(2500)],
1582 )
1583 .await
1584 .unwrap()
1585 }
1586 );
1587}