usbrh 0.1.0

A small tool and library for the USBRH (USB-connected temperature and humidity sensor). The USBRH uses Sensirion SHT11 sensor.
Documentation
#[macro_use]
extern crate log;
extern crate failure;
extern crate libusb;
#[macro_use]
extern crate failure_derive;

use libusb::{Context, Device, DeviceDescriptor, DeviceHandle, Direction, Recipient, RequestType,
             Result as UsbResult, TransferType};
use std::time::Duration;

// USBRH USB spec.
const VID: u16 = 0x1774;
const PID: u16 = 0x1001;
const SENSOR_ENDPOINT: u8 = 0x81;

// from SHT1x datasheet
const D1: f64 = -40.00;
const D2: f64 = 0.01;
const C1: f64 = -4.0;
const C2: f64 = 0.0405;
const C3: f64 = -2.8e-6;
const T1: f64 = 0.01;
const T2: f64 = 0.00008;

/// Library error
#[derive(Fail, Debug)]
pub enum UsbrhError {
    /// Target device not found
    #[fail(display = "Target device not found")]
    DeviceNotFound,
    /// Read error with error message
    #[fail(display = "Read error: {}", _0)]
    ReadError(String),
    /// Error from libusb::Error
    #[fail(display = "{}", _0)]
    Usb(#[cause] libusb::Error),
}

/// Sensor result
#[derive(Debug)]
pub struct SensorValues {
    /// Temperature in Celsius
    pub temperature: f64,
    /// Humidity in percent
    pub humidity: f64,
}

#[derive(Copy, Clone, Debug)]
struct Endpoint {
    config: u8,
    interface: u8,
    setting: u8,
    address: u8,
}

impl From<libusb::Error> for UsbrhError {
    fn from(e: libusb::Error) -> Self {
        UsbrhError::Usb(e)
    }
}

type Result<T> = std::result::Result<T, UsbrhError>;

/// read values from the sensor.
/// # Arguments
/// * `verbose` - 0 to 2 verbosity level
/// * `device_num` - target device index
pub fn read_sensors(verbose: u32, device_num: u32) -> Result<SensorValues> {
    let mut context = Context::new()?;
    if verbose >= 2 {
        context.set_log_level(libusb::LogLevel::Debug);
    }

    let ret = match open_device(&context, VID, PID, device_num)? {
        Some((mut device, device_desc, mut handle)) => {
            info!("device found");

            read_device(&mut device, &device_desc, &mut handle)
        }
        None => Err(UsbrhError::DeviceNotFound),
    };

    ret
}

fn open_device(
    context: &Context,
    vid: u16,
    pid: u16,
    device_num: u32,
) -> Result<Option<(Device, DeviceDescriptor, DeviceHandle)>> {
    let devices = match context.devices() {
        Ok(d) => d,
        Err(_) => return Ok(None),
    };
    let mut count = 0;

    for device in devices.iter() {
        let device_desc = match device.device_descriptor() {
            Ok(d) => d,
            Err(_) => continue,
        };

        if device_desc.vendor_id() == vid && device_desc.product_id() == pid {
            count += 1;
            if count == device_num {
                return device
                    .open()
                    .map(|handle| Some((device, device_desc, handle)))
                    .map_err(UsbrhError::from);
            }
        }
    }

    Ok(None)
}

fn send_control_msg(handle: &mut DeviceHandle) -> UsbResult<()> {
    let request_type: u8 =
        libusb::request_type(Direction::Out, RequestType::Class, Recipient::Interface);
    debug!("request_type: {:x}", request_type);
    let request: u8 = 0x09;
    let value: u16 = 0x0200;
    let index: u16 = 0;
    let buf = [0u8; 7];
    let timeout = Duration::from_secs(5);
    handle.write_control(request_type, request, value, index, &buf, timeout)?;
    Ok(())
}

fn read_device(
    device: &mut Device,
    device_desc: &DeviceDescriptor,
    handle: &mut DeviceHandle,
) -> Result<SensorValues> {
    handle.reset()?;

    match find_readable_endpoint(device, device_desc, TransferType::Interrupt) {
        Some(endpoint) => {
            debug!("endpoint found: {:?}", endpoint);
            read_endpoint(handle, endpoint)
        }
        None => Err(UsbrhError::ReadError(
            "No readable interrupt endpoint found".to_string(),
        )),
    }
}

fn find_readable_endpoint(
    device: &mut Device,
    device_desc: &DeviceDescriptor,
    transfer_type: TransferType,
) -> Option<Endpoint> {
    for n in 0..device_desc.num_configurations() {
        let config_desc = match device.config_descriptor(n) {
            Ok(c) => c,
            Err(_) => continue,
        };

        for interface in config_desc.interfaces() {
            for interface_desc in interface.descriptors() {
                for endpoint_desc in interface_desc.endpoint_descriptors() {
                    if endpoint_desc.direction() == Direction::In
                        && endpoint_desc.transfer_type() == transfer_type
                    {
                        return Some(Endpoint {
                            config: config_desc.number(),
                            interface: interface_desc.interface_number(),
                            setting: interface_desc.setting_number(),
                            address: endpoint_desc.address(),
                        });
                    }
                }
            }
        }
    }

    None
}

fn read_endpoint(handle: &mut DeviceHandle, endpoint: Endpoint) -> Result<SensorValues> {
    fn read(handle: &mut DeviceHandle) -> Result<SensorValues> {
        debug!("send_control_msg()");
        send_control_msg(handle)?;
        std::thread::sleep(Duration::from_secs(1));

        let mut buf = [0u8; 7];
        let timeout = Duration::from_secs(5);

        match handle.read_interrupt(SENSOR_ENDPOINT, &mut buf, timeout) {
            Ok(len) if len == buf.len() => {
                debug!("read buf: {:x?}", buf);
                Ok(parse_sensor_values(&buf))
            }
            Ok(len) => Err(UsbrhError::ReadError(format!("{} != {}", len, buf.len()))),
            Err(err) => Err(UsbrhError::from(err)),
        }
    }

    match configure_endpoint(handle, &endpoint) {
        Ok(_) => {
            let has_kernel_driver = match handle.kernel_driver_active(endpoint.interface) {
                Ok(true) => {
                    debug!("kernel driver is active");
                    handle.detach_kernel_driver(endpoint.interface)?;
                    true
                }
                _ => false,
            };
            debug!("kernel driver? {}", has_kernel_driver);

            let ret = read(handle);

            if has_kernel_driver {
                handle.attach_kernel_driver(endpoint.interface)?;
            }

            ret
        }
        Err(err) => Err(UsbrhError::from(err)),
    }
}

fn configure_endpoint(handle: &mut DeviceHandle, endpoint: &Endpoint) -> UsbResult<()> {
    debug!("set_active_configuration({})", endpoint.config);
    handle.set_active_configuration(endpoint.config)?;
    debug!("claim_interface({})", endpoint.interface);
    handle.claim_interface(endpoint.interface)?;
    debug!(
        "set_alternate_setting({}, {})",
        endpoint.interface, endpoint.setting
    );
    handle.set_alternate_setting(endpoint.interface, endpoint.setting)?;
    Ok(())
}

fn parse_sensor_values(buf: &[u8]) -> SensorValues {
    assert_eq!(buf.len(), 7);
    let temperature = (u32::from(buf[2]) << 8) | u32::from(buf[3]);
    let temperature = D1 + D2 * f64::from(temperature);

    let so_rh = f64::from(u32::from(buf[0]) << 8 | u32::from(buf[1]));
    let rh_linear = C1 + (C2 * so_rh) + (C3 * (so_rh * so_rh));
    let rh_true = (temperature - 25.0) * (T1 + T2 * so_rh) + rh_linear;

    // Values higher than 99% RH indicate fully saturated air and must be processed and displayed as 100% RH.
    SensorValues {
        temperature,
        humidity: if rh_true >= 99.0 { 100.0 } else { rh_true },
    }
}