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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
/****************************************************************************
    Copyright (c) 2015 Artyom Pavlov All Rights Reserved.

    This file is part of hidapi-rs, based on hidapi_rust by Roland Ruckerbauer.
    It's also based on the Oleg Bulatov's work (https://github.com/dmage/co2mon)
****************************************************************************/

//! Opens a KIT MT 8057 CO2 detector and reads data from it. This
//! example will not work unless such an HID is plugged in to your system.

#[cfg(all(feature = "linux-static-rusb", not(target_os = "macos")))]
extern crate rusb;

extern crate hidapi_rusb;
use hidapi_rusb::{HidApi, HidDevice};
use std::thread::sleep;
use std::time::Duration;

const CODE_TEMPERATURE: u8 = 0x42;
const CODE_CONCENTRATION: u8 = 0x50;
const HID_TIMEOUT: i32 = 5000;
const RETRY_SEC: u64 = 1;
const DEV_VID: u16 = 0x04d9;
const DEV_PID: u16 = 0xa052;
const PACKET_SIZE: usize = 8;

enum CO2Result {
    Temperature(f32),
    Concentration(u16),
    Unknown(u8, u16),
    Error(&'static str),
}

fn decode_temperature(value: u16) -> f32 {
    (value as f32) * 0.0625 - 273.15
}

fn decode_buf(buf: [u8; PACKET_SIZE]) -> CO2Result {
    let mut res: [u8; PACKET_SIZE] = [
        (buf[3] << 5) | (buf[2] >> 3),
        (buf[2] << 5) | (buf[4] >> 3),
        (buf[4] << 5) | (buf[0] >> 3),
        (buf[0] << 5) | (buf[7] >> 3),
        (buf[7] << 5) | (buf[1] >> 3),
        (buf[1] << 5) | (buf[6] >> 3),
        (buf[6] << 5) | (buf[5] >> 3),
        (buf[5] << 5) | (buf[3] >> 3),
    ];

    let magic_word = b"Htemp99e";
    for i in 0..PACKET_SIZE {
        let sub_val: u8 = (magic_word[i] << 4) | (magic_word[i] >> 4);
        res[i] = u8::overflowing_sub(res[i], sub_val).0;
    }

    if res[4] != 0x0d {
        return CO2Result::Error("Unexpected data (data[4] != 0x0d)");
    }
    let checksum = u8::overflowing_add(u8::overflowing_add(res[0], res[1]).0, res[2]).0;
    if checksum != res[3] {
        return CO2Result::Error("Checksum error");
    }

    let val: u16 = ((res[1] as u16) << 8) + res[2] as u16;
    match res[0] {
        CODE_TEMPERATURE => CO2Result::Temperature(decode_temperature(val)),
        CODE_CONCENTRATION => {
            if val > 3000 {
                CO2Result::Error("Concentration bigger than 3000 (uninitialized device?)")
            } else {
                CO2Result::Concentration(val)
            }
        }
        _ => CO2Result::Unknown(res[0], val),
    }
}

fn open_device(api: &HidApi) -> HidDevice {
    loop {
        match api.open(DEV_VID, DEV_PID) {
            Ok(dev) => return dev,
            Err(err) => {
                println!("{}", err);
                sleep(Duration::from_secs(RETRY_SEC));
            }
        }
    }
}

fn main() {
    let api = HidApi::new().expect("HID API object creation failed");

    let dev = open_device(&api);

    dev.send_feature_report(&[0; PACKET_SIZE])
        .expect("Feature report failed");

    println!(
        "Manufacurer:\t{:?}",
        dev.get_manufacturer_string()
            .expect("Failed to read manufacurer string")
    );
    println!(
        "Product:\t{:?}",
        dev.get_product_string()
            .expect("Failed to read product string")
    );
    println!(
        "Serial number:\t{:?}",
        dev.get_serial_number_string()
            .expect("Failed to read serial number")
    );

    loop {
        let mut buf = [0; PACKET_SIZE];
        match dev.read_timeout(&mut buf[..], HID_TIMEOUT) {
            Ok(PACKET_SIZE) => (),
            Ok(res) => {
                println!("Error: unexpected length of data: {}/{}", res, PACKET_SIZE);
                continue;
            }
            Err(err) => {
                println!("Error: {:}", err);
                sleep(Duration::from_secs(RETRY_SEC));
                continue;
            }
        }
        match decode_buf(buf) {
            CO2Result::Temperature(val) => println!("Temp:\t{:?}", val),
            CO2Result::Concentration(val) => println!("Conc:\t{:?}", val),
            CO2Result::Unknown(..) => (),
            CO2Result::Error(val) => {
                println!("Error:\t{}", val);
                sleep(Duration::from_secs(RETRY_SEC));
            }
        }
    }
}