megatec_ups_control/
lib.rs1use rusb::{Context, DeviceHandle, Error as UsbError, UsbContext};
2use std::time::Duration;
3use thiserror::Error;
4
5#[derive(Debug, Error)]
6pub enum UpsError {
7 #[error("USB error: {0}")]
8 Usb(#[from] UsbError),
9 #[error("Invalid response")]
10 InvalidResponse,
11 #[error("Invalid time value")]
12 InvalidTime,
13}
14
15pub type Result<T> = std::result::Result<T, UpsError>;
16
17const ASCII_MIN: u8 = 32;
18const ASCII_MAX: u8 = 126;
19const CHAR_QUOTE: u8 = 34;
20const CHAR_BACKTICK: u8 = 96;
21const CHAR_PAREN: u8 = 40;
22
23pub struct MegatecUps {
25 handle: DeviceHandle<Context>,
26 context: Context,
27}
28
29impl MegatecUps {
30 pub fn new(vendor_id: u16, product_id: u16) -> Result<Self> {
32 let context = Context::new()?;
33 let handle = context
34 .open_device_with_vid_pid(vendor_id, product_id)
35 .ok_or(UpsError::InvalidResponse)?;
36
37 Ok(Self { handle, context })
38 }
39
40 fn get_string_descriptor(&self, index: u8, length: u16) -> Result<String> {
42 let mut data = vec![0u8; length as usize];
43 let result = self.handle.read_control(
44 rusb::request_type(
45 rusb::Direction::In,
46 rusb::RequestType::Standard,
47 rusb::Recipient::Device,
48 ),
49 rusb::constants::LIBUSB_REQUEST_GET_DESCRIPTOR,
50 (rusb::constants::LIBUSB_DT_STRING as u16) << 8 | index as u16,
51 0,
52 &mut data,
53 Duration::from_secs(1),
54 )?;
55
56 if result >= 3 {
57 let filtered: String = data
58 .into_iter()
59 .filter(|&c| Self::is_valid_char(c))
60 .map(|c| c as char)
61 .collect();
62 Ok(filtered)
63 } else {
64 Err(UpsError::InvalidResponse)
65 }
66 }
67
68 fn is_valid_char(c: u8) -> bool {
70 c >= ASCII_MIN && c <= ASCII_MAX && c != CHAR_QUOTE && c != CHAR_BACKTICK && c != CHAR_PAREN
71 }
72
73 pub fn get_name(&self) -> Result<String> {
75 self.get_string_descriptor(2, 256)
76 }
77
78 pub fn get_status(&self) -> Result<UpsStatus> {
80 let _ = self.get_string_descriptor(3, 256)?;
82 std::thread::sleep(Duration::from_secs(1));
83
84 let status_str = self.get_string_descriptor(3, 256)?;
86 UpsStatus::from_str(&status_str)
87 }
88
89 pub fn get_status_no_ack(&self) -> Result<UpsStatus> {
91 let status_str = self.get_string_descriptor(3, 256)?;
92 UpsStatus::from_str(&status_str)
93 }
94
95 pub fn test(&self) -> Result<()> {
97 self.get_string_descriptor(4, 256)?;
98 Ok(())
99 }
100
101 pub fn test_until_battery_low(&self) -> Result<()> {
103 self.get_string_descriptor(5, 256)?;
104 Ok(())
105 }
106
107 pub fn test_with_time(&self, minutes: u8) -> Result<()> {
109 let calculated_time = Self::calculate_time(minutes)?;
110 self.get_string_descriptor(6, calculated_time)?;
111 Ok(())
112 }
113
114 pub fn switch_beep(&self) -> Result<()> {
116 self.get_string_descriptor(7, 256)?;
117 Ok(())
118 }
119
120 pub fn abort_test(&self) -> Result<()> {
122 self.get_string_descriptor(11, 256)?;
123 Ok(())
124 }
125
126 pub fn get_rating(&self) -> Result<String> {
128 self.get_string_descriptor(13, 256)
129 }
130
131 pub fn shutdown(&self) -> Result<()> {
133 self.get_string_descriptor(105, 2460)?;
134 Ok(())
135 }
136
137 fn calculate_time(minutes: u8) -> Result<u16> {
139 if minutes == 0 || minutes > 99 {
140 return Err(UpsError::InvalidTime);
141 }
142
143 let value = match minutes {
144 1..=9 => 100 + minutes,
145 10..=19 => 125 + (minutes - 19),
146 20..=99 => {
147 let range_start = ((minutes - 20) / 10) * 10 + 20;
148 132 + ((minutes - range_start) * 7)
149 }
150 _ => return Err(UpsError::InvalidTime),
151 };
152
153 Ok(value as u16)
154 }
155}
156
157#[derive(Debug, Clone)]
159pub struct UpsStatus {
160 pub input_voltage: f64,
161 pub input_fault_voltage: f64,
162 pub output_voltage: f64,
163 pub output_current: f64,
164 pub input_frequency: f64,
165 pub battery_voltage: f64,
166 pub temperature: f64,
167}
168
169impl UpsStatus {
170 fn from_str(status: &str) -> Result<Self> {
172 let values: Vec<f64> = status
173 .split_whitespace()
174 .take(7)
175 .map(|s| s.parse::<f64>())
176 .collect::<std::result::Result<Vec<f64>, _>>()
177 .map_err(|_| UpsError::InvalidResponse)?;
178
179 if values.len() != 7 {
180 return Err(UpsError::InvalidResponse);
181 }
182
183 Ok(Self {
184 input_voltage: values[0],
185 input_fault_voltage: values[1],
186 output_voltage: values[2],
187 output_current: values[3],
188 input_frequency: values[4],
189 battery_voltage: values[5],
190 temperature: values[6],
191 })
192 }
193}
194
195impl Drop for MegatecUps {
196 fn drop(&mut self) {
197 if let Ok(new_context) = Context::new() {
198 let _old_context = std::mem::replace(&mut self.context, new_context);
199 }
200 }
201}