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}