dynpick_force_torque_sensor/lib.rs
1//! Unofficial device driver for [Dyn Pick, Wacoh-tech force-torque sensor](https://wacoh-tech.com/en/products/dynpick/).
2//! # Examples
3//! ```no_run
4//! use dynpick_force_torque_sensor::DynpickSensorBuilder;
5//!
6//! let mut sensor = DynpickSensorBuilder::open("/dev/ttyUSB0")
7//! .and_then(|b| b.set_sensitivity_by_builtin_data())
8//! .and_then(|b| b.build())
9//! .unwrap();
10//!
11//! sensor.zeroed_next().unwrap(); // Calibration
12//!
13//! let wrench = sensor.update().unwrap();
14//! println!("Force: {}, Torque: {}", wrench.force, wrench.torque);
15//! ```
16//!
17//! # Dependency under Linux environment
18//! `libudev-dev` is required under Linux environment. Please install it by
19//! `sudo apt install libudev-dev`
20//!
21//! # Setup
22//! It may be required to customize udev rules.
23//!
24//! [This shell script](https://github.com/Amelia10007/dynpick-force-torque-sensor-rs/blob/master/examples/setup_udev_rule.sh) can be useful for customize (see the file in detail).
25//!
26//! # Note
27//! I tested this crate only by WDF-6M200-3 sensor because I have no other dynpick sensor.
28#![warn(missing_docs)]
29
30use easy_ext::ext;
31use itertools::Itertools;
32pub use pair_macro::Triplet;
33pub use serialport;
34use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits};
35use std::borrow::Cow;
36use std::fmt::{self, Display, Formatter};
37use std::marker::PhantomData;
38use std::str::FromStr;
39use std::time::Duration;
40
41/// Marker type for builder.
42pub struct Ready;
43
44/// Marker type for builder.
45pub struct SensitivityNotSetYet;
46
47/// Builder of a connection to a dynpick sensor.
48/// # Examples
49/// ```no_run
50/// use dynpick_force_torque_sensor::DynpickSensorBuilder;
51///
52/// let sensor = DynpickSensorBuilder::open("/dev/ttyUSB0")
53/// .and_then(|b| b.set_sensitivity_by_builtin_data())
54/// .and_then(|b| b.build())
55/// .unwrap();
56/// ```
57pub struct DynpickSensorBuilder<C> {
58 /// Serial port device.
59 port: Box<dyn SerialPort>,
60 /// The sensitivity of the connected sensor.
61 sensitivity: Sensitivity,
62 /// 👻
63 _calibrated: PhantomData<fn() -> C>,
64}
65
66impl DynpickSensorBuilder<SensitivityNotSetYet> {
67 /// Connects to the dynpick force torque sensor.
68 /// # Params
69 /// 1. `path` The sensor's path.
70 ///
71 /// # Returns
72 /// `Ok(builder)` if successfully connected, `Err(reason)` if failed.
73 /// Before you use the sensor, you need to calibrate the sensor by calling [`Self::set_sensitivity_by_builtin_data`] or [`Self::set_sensitivity_manually`].
74 ///
75 /// # Examples
76 /// See the example [here](`DynpickSensorBuilder`).
77 pub fn open<'a>(
78 path: impl Into<Cow<'a, str>>,
79 ) -> Result<DynpickSensorBuilder<SensitivityNotSetYet>, Error> {
80 // These settings were determined according to the hardware configuration.
81 let port = serialport::new(path, 921600)
82 .data_bits(DataBits::Eight)
83 .flow_control(FlowControl::None)
84 .parity(Parity::None)
85 .stop_bits(StopBits::One)
86 .timeout(Duration::from_millis(1))
87 .open()
88 .map_err(Error::SerialPort)?;
89
90 let builder = Self {
91 port,
92 sensitivity: Sensitivity {
93 digital_per_newton: Triplet::default(),
94 digital_per_newtonmeter: Triplet::default(),
95 },
96 _calibrated: PhantomData,
97 };
98
99 Ok(builder)
100 }
101
102 /// Set the [`Sensitivity`] of the connected sensor by using the specified sensitivity.
103 /// # Examples
104 /// ```no_run
105 /// use dynpick_force_torque_sensor::{DynpickSensorBuilder, Sensitivity, Triplet};
106 ///
107 /// let sensitivity = {
108 /// let force = Triplet::new(24.9, 24.6, 24.5);
109 /// let torque = Triplet::new(1664.7, 1639.7, 1638.0);
110 /// Sensitivity::new(force, torque)
111 /// };
112 ///
113 /// let sensor = DynpickSensorBuilder::open("/dev/ttyUSB0")
114 /// .map(|b| b.set_sensitivity_manually(sensitivity))
115 /// .and_then(|b| b.build())
116 /// .unwrap();
117 /// ```
118 pub fn set_sensitivity_manually(self, sensitivity: Sensitivity) -> DynpickSensorBuilder<Ready> {
119 DynpickSensorBuilder {
120 port: self.port,
121 sensitivity,
122 _calibrated: PhantomData,
123 }
124 }
125
126 /// Set the [`Sensitivity`] of the connected sensor, reading its sensitivity from it.
127 /// Some sensors may not support this functionality (`Err(_)` will be returned under this situation).
128 /// # Examples
129 /// See the example [here](`DynpickSensorBuilder`)
130 ///
131 /// # Note
132 /// This method has not been tested yet because my sensor (WDF-6M200-3) does not support this functionality.
133 pub fn set_sensitivity_by_builtin_data(mut self) -> Result<DynpickSensorBuilder<Ready>, Error> {
134 const SENSITIVITY_RESPONSE_LENGTH: usize = 46;
135
136 // Send and wait.
137 self.port.write_all(&['p' as u8]).map_err(Error::IO)?;
138 std::thread::sleep(self.port.timeout());
139
140 let mut res = [0; SENSITIVITY_RESPONSE_LENGTH];
141 self.port.read_exact(&mut res).map_err(Error::IO)?;
142
143 let res = std::str::from_utf8(&res).or(Err(Error::Utf8(res.to_vec())))?;
144 let (fx, fy, fz, mx, my, mz) = res
145 .split(',')
146 .map(f64::from_str)
147 .filter_map(Result::ok)
148 .next_tuple()
149 .ok_or(Error::ParseResponse(res.to_owned()))?;
150
151 let force = Triplet::new(fx, fy, fz);
152 let torque = Triplet::new(mx, my, mz);
153
154 let sensitivity = Sensitivity::new(force, torque);
155
156 Ok(self.set_sensitivity_manually(sensitivity))
157 }
158}
159
160impl DynpickSensorBuilder<Ready> {
161 /// Consuming this builder, attempts to construct a sensor instance.
162 pub fn build(self) -> Result<DynpickSensor, Error> {
163 let mut sensor = DynpickSensor {
164 port: self.port,
165 last_wrench: Wrench::zeroed(),
166 sensitivity: self.sensitivity,
167 };
168
169 // First single data request.
170 sensor.request_next_wrench()?;
171
172 Ok(sensor)
173 }
174}
175
176/// Dynpick 6-axis force-torque sensor.
177pub struct DynpickSensor {
178 /// Serial port device.
179 port: Box<dyn SerialPort>,
180 /// The latest wrench acquired by `update`.
181 last_wrench: Wrench,
182 /// The sensitivity of the connected sensor.
183 sensitivity: Sensitivity,
184}
185
186impl DynpickSensor {
187 /// Returns the latest wrench that is stored in this instance without communicaing the sensor.
188 ///
189 /// Use [`Self::update`] instead to obtain a new wrench from the sensor.
190 /// # Returns
191 /// `Ok(sensor)` if successfully connected, `Err(reason)` if failed.
192 pub fn last_wrench(&self) -> Wrench {
193 self.last_wrench
194 }
195
196 /// Returns the sensitivity of this sensor.
197 pub fn sensitivity(&self) -> Sensitivity {
198 self.sensitivity
199 }
200
201 /// Communicating to the sensor, updates the latest wrench.
202 /// # Returns
203 /// `Ok(wrench)` if succeeds, `Err(reason)` if failed.
204 pub fn update(&mut self) -> Result<Wrench, Error> {
205 const WRENCH_RESPONSE_LENGTH: usize = 27;
206
207 // Regardless of success or failure of receive and parse the messsage, request the next single data.
208 // If we do not so, after updating failed once, updating will fail everytime due to no reception from the sensor.
209 let mut res = [0; WRENCH_RESPONSE_LENGTH];
210 self.port
211 .read_exact(&mut res)
212 .map_err(Error::IO)
213 .finalize(|| self.request_next_wrench())?;
214
215 let res = std::str::from_utf8(&res)
216 .or(Err(Error::Utf8(res.to_vec())))
217 .finalize(|| self.request_next_wrench())?;
218
219 let (fx, fy, fz, mx, my, mz) = (0..6)
220 .map(|i| 1 + i * 4)
221 .map(|start| &res[start..start + 4])
222 .map(|src| i32::from_str_radix(src, 16))
223 .filter_map(Result::ok)
224 .next_tuple()
225 .ok_or(Error::ParseResponse(res.to_owned()))
226 .finalize(|| self.request_next_wrench())?;
227
228 let digital_force = Triplet::new(fx, fy, fz);
229 let digital_torque = Triplet::new(mx, my, mz);
230
231 let force = digital_force
232 .map(|d| d - 8192)
233 .map(|d| d as f64)
234 .map_entrywise(self.sensitivity.digital_per_newton, |d, s| d / s);
235
236 let torque = digital_torque
237 .map(|d| d - 8192)
238 .map(|d| d as f64)
239 .map_entrywise(self.sensitivity.digital_per_newtonmeter, |d, s| d / s);
240
241 self.last_wrench = Wrench::new(force, torque);
242
243 // Send a request to obtain a new wrench.
244 self.request_next_wrench()?;
245
246 Ok(self.last_wrench)
247 }
248
249 /// If this method succeeds, the next wrench acquired by [`Self::update`] will be zeroed.
250 /// This methos is useful for zero-point calibration.
251 /// # Examples
252 /// ```no_run
253 /// use dynpick_force_torque_sensor::{DynpickSensorBuilder, Triplet};
254 ///
255 /// let mut sensor = DynpickSensorBuilder::open("/dev/ttyUSB0")
256 /// .and_then(|b| b.set_sensitivity_by_builtin_data())
257 /// .and_then(|b| b.build())
258 /// .unwrap();
259 ///
260 /// sensor.zeroed_next().unwrap();
261 ///
262 /// let wrench = sensor.update().unwrap();
263 ///
264 /// assert_eq!(wrench.force, Triplet::new(0.0, 0.0, 0.0));
265 /// assert_eq!(wrench.torque, Triplet::new(0.0, 0.0, 0.0));
266 /// ```
267 pub fn zeroed_next(&mut self) -> Result<(), Error> {
268 self.port.write_all(&['O' as u8]).map_err(Error::IO)
269 }
270
271 /// Reads the product info from the sensor.
272 /// # Returns
273 /// `Ok(product_info)` if succeeds, `Err(reason)` if failed.
274 pub fn receive_product_info(&mut self) -> Result<String, Error> {
275 // Buffer may not be empty due to request_next_wrench() or its response.
276 self.port
277 .clear(serialport::ClearBuffer::All)
278 .map_err(Error::SerialPort)?;
279
280 // Send and wait.
281 self.port.write_all(&['V' as u8]).map_err(Error::IO)?;
282 std::thread::sleep(self.port.timeout());
283
284 // The response may be non-fixed size.
285 let bytes = self.port.bytes_to_read().map_err(Error::SerialPort)?;
286 let mut res = vec![0; bytes as usize];
287
288 let info = match self.port.read(&mut res) {
289 Ok(_) => match std::str::from_utf8(&res) {
290 Ok(str) => Ok(str.to_owned()),
291 Err(_) => Err(Error::Utf8(res)),
292 },
293 Err(e) => Err(Error::IO(e)),
294 };
295
296 // Restart sensing wrenches.
297 self.request_next_wrench()?;
298
299 info
300 }
301
302 /// Returns the reference to the serial port for the sensor.
303 pub fn inner_port(&self) -> &Box<dyn SerialPort> {
304 &self.port
305 }
306
307 /// Request single wrench.
308 fn request_next_wrench(&mut self) -> Result<(), Error> {
309 self.port.write_all(&['R' as u8]).map_err(Error::IO)
310 }
311}
312
313/// Represents an error occurred while communicating sensors.
314#[derive(Debug)]
315pub enum Error {
316 /// Failed to manipulate the port for the sensor.
317 SerialPort(serialport::Error),
318 /// Failed to read or write data during communication.
319 IO(std::io::Error),
320 /// The received data cannot be interpleted with UTF-8.
321 /// Inner value is the raw reception.
322 Utf8(Vec<u8>),
323 /// Received an unexpected format string.
324 /// Inner value is the raw string.
325 ParseResponse(String),
326}
327
328impl Display for Error {
329 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
330 match self {
331 Error::SerialPort(e) => write!(f, "SerialPort: {}", e),
332 Error::IO(e) => write!(f, "IO: {}", e),
333 Error::Utf8(v) => write!(
334 f,
335 "The response from the sensor is invalid for utf8. Raw response: {:X?}",
336 v
337 ),
338 Error::ParseResponse(res) => write!(
339 f,
340 "Failed to parse the response from the sensor. The response: {}",
341 res
342 ),
343 }
344 }
345}
346
347impl std::error::Error for Error {
348 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
349 match self {
350 Error::SerialPort(e) => Some(e),
351 Error::IO(e) => Some(e),
352 _ => None,
353 }
354 }
355}
356
357/// A pair of force and torque.
358#[derive(Debug, Copy, Clone, PartialEq)]
359pub struct Wrench {
360 /// 3-dimensional force in Newton.
361 pub force: Triplet<f64>,
362 /// 3-dimensional torque in NewtonMeter.
363 pub torque: Triplet<f64>,
364}
365
366impl Wrench {
367 /// Returns a new wrench.
368 pub fn new(force: Triplet<f64>, torque: Triplet<f64>) -> Wrench {
369 Self { force, torque }
370 }
371
372 /// Returns a new wrench, initializing it to 0 Newton and 0 NewtonMeter.
373 pub fn zeroed() -> Wrench {
374 Wrench::new(Triplet::default(), Triplet::default())
375 }
376}
377
378/// How much the digital value from the sensor increses per 1 Newton (for force) and per 1 NewtonMeter (for torque).
379#[derive(Debug, Clone, Copy, PartialEq)]
380pub struct Sensitivity {
381 /// How much the digital value from the sensor increses per 1 Newton.
382 digital_per_newton: Triplet<f64>,
383 /// How much the digital value from the sensor increses per 1 NewtonMeter.
384 digital_per_newtonmeter: Triplet<f64>,
385}
386
387impl Sensitivity {
388 /// Initialize a new sensitivity of a sensor.
389 /// # Params
390 /// 1. `digital_per_newton` How much the digital value from the sensor increses per 1 Newton.
391 /// 1. `digital_per_newtonmeter` How much the digital value from the sensor increses per 1 NewtonMeter.
392 pub fn new(
393 digital_per_newton: Triplet<f64>,
394 digital_per_newtonmeter: Triplet<f64>,
395 ) -> Sensitivity {
396 Self {
397 digital_per_newton,
398 digital_per_newtonmeter,
399 }
400 }
401}
402
403// Helper trait
404#[ext]
405impl<T, E> Result<T, E> {
406 /// # Returns
407 /// `Ok(v)` without calling `f` if itself is `Ok(v)`.
408 /// `Err(e1)` if itself if `Err(e1)` and `f` returns `Ok()`.
409 /// `Err(e2)` if itself if `Err(e1)` and `f` returns `Err(e2)`.
410 fn finalize<U, F>(self, f: F) -> Self
411 where
412 F: FnOnce() -> Result<U, E>,
413 {
414 match self {
415 Ok(value) => Ok(value),
416 Err(e1) => match f() {
417 Ok(_) => Err(e1),
418 Err(e2) => Err(e2),
419 },
420 }
421 }
422}