1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Weighty is a simple library for reading from USB HID scales, as well as an
//! example utility that consumes the library and performs one reading on all
//! available scales.
//!
//! If you have a little compilation time to spare, I recommend using the `units`
//! feature to enable the `read` method on the drivers, which prevents you from
//! bungling the units... not that you would... but I have.
//!
//! Example:
//! ```
//! use weighty;
//!
//! for scale in weighty::get_all_scales() {
//!     println!("{:?}kgf", scale.read_kilograms());
//! }
//! ```
//!

#![deny(missing_docs)]
#![forbid(unsafe_code)]

use hidapi::{HidApi, HidDevice};
#[cfg(feature = "units")]
use uom::si::f64::Force;

mod dymo;

/// A list of things that can go wrong when reading from a digital scale.
#[derive(Clone, Debug)]
pub enum HidScaleError {
    /// For some reason we can't read from the scale.
    CantReadDueTo(String),
    /// For some reason the scale didn't send the expected number of bytes.  If
    /// you see this error please file a bug report.
    NotEnoughData,
    /// The scale has more weight on it than it can handle / measure.
    Overloaded,
    /// For some reason the scale is reporting units that we don't understand.
    /// If you see this error please file a bug report.
    UnknownUnits,
    /// Some scales, for example Dymo scales, cannot send back weight differences
    /// (as you would compute using the tare feature).  If the scale is reporting
    /// such a condition, this is the error you'll get.
    UnreportableReading,
}

/// Type alias to simplify signatures.
pub type Result<T> = std::result::Result<T, HidScaleError>;

/// A ScaleDriver takes care of decoding and making sense of the raw bytes
/// coming from the HID scale.
pub trait ScaleDriver {
    #[cfg(feature = "units")]
    /// Returns the amount of force (weight) currently bearing on the scale.
    fn read(&self) -> Result<Force>;

    /// Returns the number of kilograms-force currently bearing on the scale.
    fn read_kilograms(&self) -> Result<f64>;

    /// Returns the number of pounds of force currently bearing on the scale.
    fn read_pounds(&self) -> Result<f64>;
}

/// Returns a collection of drivers for all of the currently available scales.
///
/// If this is not returning a scale that you expect to see, double check that
/// your user has permissions to access the device.  You might need to set up a
/// udev rule and then reconnect your device.
pub fn get_all_scales() -> Vec<Box<dyn ScaleDriver>> {
    let api = HidApi::new().expect("Couldn't aquire the HID API???");
    api.device_list()
        .filter_map(|info| {
            let vendor_id = info.vendor_id();
            let product_id = info.product_id();
            info.open_device(&api).ok().and_then(|dev| make_driver(vendor_id, product_id, dev))
        })
        .collect()
}

/// Returns all of one specific type of scale that are currently connected.
///
/// If this is not returning a scale that you expect to see, double check that
/// your user has permissions to access the device.  You might need to set up a
/// udev rule and then reconnect your device.
pub fn get_scales_by_usb_id(vendor_id: u16, product_id: u16) -> Vec<Box<dyn ScaleDriver>> {
    let api = HidApi::new().expect("Couldn't aquire the HID API???");
    api.device_list()
        .filter_map(|info| {
            let vid = info.vendor_id();
            let pid = info.product_id();
            if vid != vendor_id || pid != product_id {
                return None;
            }
            info.open_device(&api).ok().and_then(|dev| make_driver(vendor_id, product_id, dev))
        })
        .collect()
}

/// Returns one specific scale, if it is connected and we have access to it.
pub fn get_scale_by_serial_number(serial_number: &str) -> Option<Box<dyn ScaleDriver>> {
    let api = HidApi::new().expect("Couldn't aquire the HID API???");
    let result = api
        .device_list()
        .filter_map(|info| {
            let vendor_id = info.vendor_id();
            let product_id = info.product_id();

            info.open_device(&api).ok().and_then(|dev| match dev.get_serial_number_string() {
                Ok(Some(s)) if s == serial_number => make_driver(vendor_id, product_id, dev),
                _ => None,
            })
        })
        .next();
    result
}

fn make_driver(vendor_id: u16, product_id: u16, device: HidDevice) -> Option<Box<dyn ScaleDriver>> {
    match vendor_id {
        dymo::VENDOR_ID => dymo::make_driver(product_id, device),
        _ => None,
    }
}