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
extern crate hidapi;
pub mod util;
pub use hidapi::HidApi;
use hidapi::HidDevice;
const VENDOR_ID: u16 = 0x04d8;
const PRODUCT_ID: u16 = 0x003f;
const REQUEST_DATA: u8 = 0x37;
const REQUEST_STARTSTOP: u8 = 0x80;
const REQUEST_STATUS: u8 = 0x81;
const REQUEST_ONOFF: u8 = 0x82;
const REQUEST_VERSION: u8 = 0x83;
const STATUS_ON: u8 = 0x01;
const STATUS_STARTED: u8 = 0x01;
const BUF_SIZE: usize = 65;
pub type HospResult<T> = Result<T, &'static str>;
pub struct HospDevice<'a> {
dev: HidDevice<'a>,
pub timeout_ms: i32,
}
pub struct HospStatus {
pub on: bool,
pub started: bool,
}
pub struct HospData {
pub m_volts: u32,
pub m_amps: Option<u32>,
pub m_watts: Option<u32>,
pub m_watt_hours: Option<u32>,
}
fn to_milliunits(bytes: &[u8]) -> Option<u32> {
match bytes[0] as char {
'-' => None,
_ => String::from_utf8_lossy(bytes).trim_left()
.parse::<f32>()
.ok()
.map(|val| (val * 1000.0) as u32)
}
}
impl<'a> HospDevice<'a> {
pub fn from_hid(hid: &'a HidApi) -> HospResult<Self> {
hid.open(VENDOR_ID, PRODUCT_ID).map(|dev| HospDevice{ dev: dev, timeout_ms: 0 })
}
fn write(&self, t: u8) -> HospResult<()> {
self.dev.write(&[0, t]).map(|_| ())
}
fn read(&self, t: u8) -> HospResult<Option<[u8; BUF_SIZE]>> {
let mut buf = [0; BUF_SIZE];
buf[1] = t;
self.dev.read_timeout(&mut buf, self.timeout_ms).map(|_| if buf[0] == t { Some(buf) } else { None })
}
pub fn get_version(&self) -> HospResult<Option<String>> {
self.write(REQUEST_VERSION).and(self.read(REQUEST_VERSION).map(|opt| opt.map(|buf|
String::from_utf8_lossy(&buf[1..17]).into_owned()
)))
}
pub fn get_status(&self) -> HospResult<Option<HospStatus>> {
self.write(REQUEST_STATUS).and(self.read(REQUEST_STATUS).map(|opt| opt.map(|buf|
HospStatus {
on: buf[2] == STATUS_ON,
started: buf[1] == STATUS_STARTED,
}
)))
}
pub fn toggle_onoff(&self) -> HospResult<()> {
self.write(REQUEST_ONOFF)
}
pub fn toggle_startstop(&self) -> HospResult<()> {
self.write(REQUEST_STARTSTOP)
}
pub fn get_data(&self) -> HospResult<Option<HospData>> {
self.write(REQUEST_DATA).and(self.read(REQUEST_DATA).map(|opt| opt.map(|buf|
HospData {
m_volts: to_milliunits(&buf[2..7]).unwrap(),
m_amps: to_milliunits(&buf[10..15]),
m_watts: to_milliunits(&buf[17..23]),
m_watt_hours: to_milliunits(&buf[24..31])
}
)))
}
}