razer-battery-report/src/controller.rs

210 lines
6.5 KiB
Rust
Raw Normal View History

2024-08-27 18:08:03 +00:00
use hidapi::{HidApi, HidDevice};
use log::{info, warn};
use std::ffi::CString;
use std::thread;
use std::time::Duration;
use crate::devices::RAZER_DEVICE_LIST;
const MAX_TRIES_SEND: u8 = 10;
const TIME_BETWEEN_SEND: Duration = Duration::from_millis(500);
pub struct RazerReport {
pub status: u8,
pub transaction_id: u8,
pub remaining_packets: u16,
pub protocol_type: u8,
pub data_size: u8,
pub command_class: u8,
pub command_id: u8,
pub arguments: [u8; 80],
pub crc: u8,
pub reserved: u8,
}
impl RazerReport {
pub const STATUS_NEW_COMMAND: u8 = 0x00;
pub const STATUS_BUSY: u8 = 0x01;
pub const STATUS_SUCCESSFUL: u8 = 0x02;
pub const STATUS_FAILURE: u8 = 0x03;
pub const STATUS_NO_RESPONSE: u8 = 0x04;
pub const STATUS_NOT_SUPPORTED: u8 = 0x05;
pub fn new() -> Self {
RazerReport {
status: 0,
transaction_id: 0,
remaining_packets: 0,
protocol_type: 0,
data_size: 0,
command_class: 0,
command_id: 0,
arguments: [0; 80],
crc: 0,
reserved: 0,
}
}
pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
if data.len() != 90 {
return Err("Expected 90 bytes of data as razer report");
}
let mut report = RazerReport::new();
report.status = data[0];
report.transaction_id = data[1];
report.remaining_packets = u16::from_be_bytes([data[2], data[3]]);
report.protocol_type = data[4];
report.data_size = data[5];
report.command_class = data[6];
report.command_id = data[7];
report.arguments.copy_from_slice(&data[8..88]);
report.crc = data[88];
report.reserved = data[89];
Ok(report)
}
pub fn pack(&self) -> Vec<u8> {
let mut data = vec![
self.status,
self.transaction_id,
(self.remaining_packets >> 8) as u8,
(self.remaining_packets & 0xFF) as u8,
self.protocol_type,
self.data_size,
self.command_class,
self.command_id,
];
data.extend_from_slice(&self.arguments);
data.push(self.crc);
data.push(self.reserved);
data
}
pub fn calculate_crc(&self) -> u8 {
let data = self.pack();
data[2..88].iter().fold(0, |crc, &byte| crc ^ byte)
}
pub fn is_valid(&self) -> bool {
self.calculate_crc() == self.crc
}
}
pub struct DeviceController {
pub handle: HidDevice,
pub name: String,
pub pid: u16,
pub report_id: u8,
pub transaction_id: u8,
}
impl DeviceController {
pub fn new(name: String, pid: u16, path: String) -> Result<Self, Box<dyn std::error::Error>> {
let api = HidApi::new()?;
// Convert the path String to a CString
let c_path = CString::new(path)?;
let handle = api.open_path(c_path.as_ref())?;
let transaction_id = RAZER_DEVICE_LIST
.iter()
.find(|device| device.pid == pid)
.map_or(0x3F, |device| device.transaction_id());
Ok(DeviceController {
handle,
name,
pid,
report_id: 0x00,
transaction_id,
})
}
pub fn get_battery_level(&self) -> Result<i32, Box<dyn std::error::Error>> {
let request = self.create_command(0x07, 0x80, 0x02);
let response = self.send_payload(request)?;
let battery_level = (response.arguments[1] as f32 / 255.0) * 100.0;
// println!("{}\t battery level: {:.2}%", self.name, battery_level);
Ok(battery_level.round() as i32)
}
pub fn get_charging_status(&self) -> Result<bool, Box<dyn std::error::Error>> {
let request = self.create_command(0x07, 0x84, 0x02);
let response = self.send_payload(request)?;
let charging_status = response.arguments[1] != 0;
// println!("{}\t charging status: {}", self.name, charging_status);
Ok(charging_status)
}
pub fn send_payload(
&self,
mut request: RazerReport,
) -> Result<RazerReport, Box<dyn std::error::Error>> {
request.crc = request.calculate_crc();
for _ in 0..MAX_TRIES_SEND {
self.usb_send(&request)?;
let response = self.usb_receive()?;
if response.remaining_packets != request.remaining_packets
|| response.command_class != request.command_class
|| response.command_id != request.command_id
{
return Err("Response doesn't match request".into());
}
match response.status {
RazerReport::STATUS_SUCCESSFUL => return Ok(response),
RazerReport::STATUS_BUSY => info!("Device is busy"),
RazerReport::STATUS_NO_RESPONSE => info!("Command timed out"),
RazerReport::STATUS_NOT_SUPPORTED => return Err("Command not supported".into()),
RazerReport::STATUS_FAILURE => return Err("Command failed".into()),
_ => return Err("Error unknown report status".into()),
}
thread::sleep(TIME_BETWEEN_SEND);
warn!("Trying to resend command");
}
Err(format!("Abort command (tries: {})", MAX_TRIES_SEND).into())
}
pub fn create_command(&self, command_class: u8, command_id: u8, data_size: u8) -> RazerReport {
let mut report = RazerReport::new();
report.status = RazerReport::STATUS_NEW_COMMAND;
report.transaction_id = self.transaction_id;
report.command_class = command_class;
report.command_id = command_id;
report.data_size = data_size;
report
}
pub fn usb_send(&self, report: &RazerReport) -> Result<(), Box<dyn std::error::Error>> {
let mut data = vec![self.report_id];
data.extend_from_slice(&report.pack());
self.handle.send_feature_report(&data)?;
thread::sleep(Duration::from_millis(60));
Ok(())
}
pub fn usb_receive(&self) -> Result<RazerReport, Box<dyn std::error::Error>> {
let expected_length = 91;
let mut buf = vec![0u8; expected_length];
let bytes_read = self.handle.get_feature_report(&mut buf)?;
if bytes_read != expected_length {
return Err("Error while getting feature report".into());
}
let report = RazerReport::from_bytes(&buf[1..])?;
if !report.is_valid() {
return Err("Get report has no valid crc".into());
}
Ok(report)
}
}