use anyhow::{anyhow, Result};
use rusb::{Device, DeviceHandle, GlobalContext};
use std::time::Duration;
use thiserror::Error;
const VENDOR_ID: u16 = 0x191A;
const DEVICE_ID: u16 = 0x6001;
const COMMAND_VERSION: u8 = 0x0;
const COMMAND_ID_CONTROL: u8 = 0x0;
const COMMAND_ID_SETTING: u8 = 0x1;
const COMMAND_ID_GETSTATE: u8 = 0x80;
const ENDPOINT_ADDRESS: u8 = 0x01;
const ENDPOINT_ADDRESS_GET: u8 = 0x81;
const SEND_TIMEOUT: Duration = Duration::from_millis(1000);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum LedColor {
Off = 0,
Red = 1,
Green = 2,
Yellow = 3,
Blue = 4,
Purple = 5,
LightBlue = 6,
White = 7,
Keep = 0xF,
}
impl LedColor {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"off" => Some(LedColor::Off),
"red" => Some(LedColor::Red),
"green" => Some(LedColor::Green),
"yellow" => Some(LedColor::Yellow),
"blue" => Some(LedColor::Blue),
"purple" => Some(LedColor::Purple),
"lightblue" | "light-blue" | "skyblue" | "sky-blue" => Some(LedColor::LightBlue),
"white" => Some(LedColor::White),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum LedPattern {
Off = 0x0,
On = 0x1,
Pattern1 = 0x2,
Pattern2 = 0x3,
Pattern3 = 0x4,
Pattern4 = 0x5,
Pattern5 = 0x6,
Pattern6 = 0x7,
Keep = 0xF,
}
impl LedPattern {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"off" => Some(LedPattern::Off),
"on" | "solid" => Some(LedPattern::On),
"pattern1" | "1" => Some(LedPattern::Pattern1),
"pattern2" | "2" => Some(LedPattern::Pattern2),
"pattern3" | "3" => Some(LedPattern::Pattern3),
"pattern4" | "4" => Some(LedPattern::Pattern4),
"pattern5" | "5" => Some(LedPattern::Pattern5),
"pattern6" | "6" => Some(LedPattern::Pattern6),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum BuzzerPattern {
Off = 0x0,
On = 0x1,
Sweep = 0x2,
Intermittent = 0x3,
WeakAttention = 0x4,
StrongAttention = 0x5,
ShiningStar = 0x6,
LondonBridge = 0x7,
Keep = 0xF,
}
impl BuzzerPattern {
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"off" => Some(BuzzerPattern::Off),
"on" | "continuous" => Some(BuzzerPattern::On),
"sweep" => Some(BuzzerPattern::Sweep),
"intermittent" => Some(BuzzerPattern::Intermittent),
"weak" | "weak-attention" => Some(BuzzerPattern::WeakAttention),
"strong" | "strong-attention" => Some(BuzzerPattern::StrongAttention),
"shining-star" => Some(BuzzerPattern::ShiningStar),
"london-bridge" => Some(BuzzerPattern::LondonBridge),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuzzerCount(u8);
impl BuzzerCount {
pub const CONTINUOUS: Self = Self(0x0);
pub const KEEP: Self = Self(0xF);
pub fn times(count: u8) -> Option<Self> {
if count >= 1 && count <= 14 {
Some(Self(count))
} else {
None
}
}
pub fn value(&self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BuzzerVolume(u8);
impl BuzzerVolume {
pub const OFF: Self = Self(0x0);
pub const MAX: Self = Self(0xA);
pub const KEEP: Self = Self(0xF);
pub fn level(level: u8) -> Option<Self> {
if level <= 0xA {
Some(Self(level))
} else {
None
}
}
pub fn value(&self) -> u8 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Setting {
Off = 0x0,
On = 0x1,
}
#[derive(Debug, Error)]
pub enum BeaconError {
#[error("USB error: {0}")]
Usb(#[from] rusb::Error),
#[error("Device not found")]
DeviceNotFound,
#[error("Failed to send command")]
SendFailed,
#[error("Invalid parameter")]
InvalidParameter,
}
pub struct Beacon {
handle: DeviceHandle<GlobalContext>,
_device: Device<GlobalContext>,
}
impl Beacon {
pub fn open() -> Result<Self> {
let devices = rusb::devices()?;
for device in devices.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == VENDOR_ID && device_desc.product_id() == DEVICE_ID {
let handle = device.open()?;
if let Ok(active) = handle.kernel_driver_active(0) {
if active {
handle.detach_kernel_driver(0)?;
}
}
handle.set_active_configuration(1)?;
handle.claim_interface(0)?;
return Ok(Beacon {
handle,
_device: device,
});
}
}
Err(anyhow!("Device not found"))
}
pub fn scan() -> Result<Vec<(u8, u8, u16, u16)>> {
let devices = rusb::devices()?;
let mut found = Vec::new();
for device in devices.iter() {
let device_desc = device.device_descriptor()?;
if device_desc.vendor_id() == VENDOR_ID && device_desc.product_id() == DEVICE_ID {
found.push((
device.bus_number(),
device.address(),
device_desc.vendor_id(),
device_desc.product_id(),
));
}
}
Ok(found)
}
fn send_command(&self, data: &[u8]) -> Result<()> {
let bytes_written = self.handle.write_bulk(ENDPOINT_ADDRESS, data, SEND_TIMEOUT)?;
if bytes_written != data.len() {
return Err(anyhow!("Failed to send complete command"));
}
Ok(())
}
pub fn set_light(&self, color: LedColor, pattern: LedPattern) -> Result<()> {
let buzzer_control = (BuzzerCount::KEEP.value() << 4) | BuzzerPattern::Keep as u8;
let led_control = ((color as u8) << 4) | (pattern as u8);
let data = [
COMMAND_VERSION,
COMMAND_ID_CONTROL,
buzzer_control,
BuzzerVolume::KEEP.value(),
led_control,
0, 0, 0,
];
self.send_command(&data)
}
pub fn set_buzzer(&self, pattern: BuzzerPattern, count: BuzzerCount) -> Result<()> {
let buzzer_control = (count.value() << 4) | (pattern as u8);
let led_control = ((LedColor::Keep as u8) << 4) | (LedPattern::Keep as u8);
let data = [
COMMAND_VERSION,
COMMAND_ID_CONTROL,
buzzer_control,
BuzzerVolume::KEEP.value(),
led_control,
0, 0, 0,
];
self.send_command(&data)
}
pub fn set_volume(&self, volume: BuzzerVolume) -> Result<()> {
let buzzer_control = (BuzzerCount::KEEP.value() << 4) | (BuzzerPattern::Keep as u8);
let led_control = ((LedColor::Keep as u8) << 4) | (LedPattern::Keep as u8);
let data = [
COMMAND_VERSION,
COMMAND_ID_CONTROL,
buzzer_control,
volume.value(),
led_control,
0, 0, 0,
];
self.send_command(&data)
}
pub fn set_buzzer_ex(&self, pattern: BuzzerPattern, count: BuzzerCount, volume: BuzzerVolume) -> Result<()> {
let buzzer_control = (count.value() << 4) | (pattern as u8);
let led_control = ((LedColor::Keep as u8) << 4) | (LedPattern::Keep as u8);
let data = [
COMMAND_VERSION,
COMMAND_ID_CONTROL,
buzzer_control,
volume.value(),
led_control,
0, 0, 0,
];
self.send_command(&data)
}
pub fn set_setting(&self, setting: Setting) -> Result<()> {
let data = [
COMMAND_VERSION,
COMMAND_ID_SETTING,
setting as u8,
0, 0, 0, 0, 0,
];
self.send_command(&data)
}
pub fn get_touch_sensor_state(&self) -> Result<bool> {
let data = [
COMMAND_VERSION,
COMMAND_ID_GETSTATE,
0, 0, 0, 0, 0, 0,
];
self.send_command(&data)?;
let mut response = [0u8; 2];
self.handle.read_bulk(ENDPOINT_ADDRESS_GET, &mut response, SEND_TIMEOUT)?;
Ok((response[1] & 1) == 1)
}
pub fn reset(&self) -> Result<()> {
let buzzer_control = (BuzzerCount::KEEP.value() << 4) | (BuzzerPattern::Off as u8);
let led_control = ((LedColor::Off as u8) << 4) | (LedPattern::Off as u8);
let data = [
COMMAND_VERSION,
COMMAND_ID_CONTROL,
buzzer_control,
BuzzerVolume::KEEP.value(),
led_control,
0, 0, 0,
];
self.send_command(&data)
}
}
impl Drop for Beacon {
fn drop(&mut self) {
let _ = self.handle.release_interface(0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_led_color_from_str() {
assert_eq!(LedColor::from_str("off"), Some(LedColor::Off));
assert_eq!(LedColor::from_str("red"), Some(LedColor::Red));
assert_eq!(LedColor::from_str("RED"), Some(LedColor::Red));
assert_eq!(LedColor::from_str("green"), Some(LedColor::Green));
assert_eq!(LedColor::from_str("yellow"), Some(LedColor::Yellow));
assert_eq!(LedColor::from_str("blue"), Some(LedColor::Blue));
assert_eq!(LedColor::from_str("purple"), Some(LedColor::Purple));
assert_eq!(LedColor::from_str("lightblue"), Some(LedColor::LightBlue));
assert_eq!(LedColor::from_str("light-blue"), Some(LedColor::LightBlue));
assert_eq!(LedColor::from_str("skyblue"), Some(LedColor::LightBlue));
assert_eq!(LedColor::from_str("sky-blue"), Some(LedColor::LightBlue));
assert_eq!(LedColor::from_str("white"), Some(LedColor::White));
assert_eq!(LedColor::from_str("invalid"), None);
}
#[test]
fn test_led_color_values() {
assert_eq!(LedColor::Off as u8, 0);
assert_eq!(LedColor::Red as u8, 1);
assert_eq!(LedColor::Green as u8, 2);
assert_eq!(LedColor::Yellow as u8, 3);
assert_eq!(LedColor::Blue as u8, 4);
assert_eq!(LedColor::Purple as u8, 5);
assert_eq!(LedColor::LightBlue as u8, 6);
assert_eq!(LedColor::White as u8, 7);
assert_eq!(LedColor::Keep as u8, 0xF);
}
#[test]
fn test_led_pattern_from_str() {
assert_eq!(LedPattern::from_str("off"), Some(LedPattern::Off));
assert_eq!(LedPattern::from_str("on"), Some(LedPattern::On));
assert_eq!(LedPattern::from_str("solid"), Some(LedPattern::On));
assert_eq!(LedPattern::from_str("pattern1"), Some(LedPattern::Pattern1));
assert_eq!(LedPattern::from_str("1"), Some(LedPattern::Pattern1));
assert_eq!(LedPattern::from_str("pattern2"), Some(LedPattern::Pattern2));
assert_eq!(LedPattern::from_str("2"), Some(LedPattern::Pattern2));
assert_eq!(LedPattern::from_str("pattern3"), Some(LedPattern::Pattern3));
assert_eq!(LedPattern::from_str("3"), Some(LedPattern::Pattern3));
assert_eq!(LedPattern::from_str("pattern4"), Some(LedPattern::Pattern4));
assert_eq!(LedPattern::from_str("4"), Some(LedPattern::Pattern4));
assert_eq!(LedPattern::from_str("pattern5"), Some(LedPattern::Pattern5));
assert_eq!(LedPattern::from_str("5"), Some(LedPattern::Pattern5));
assert_eq!(LedPattern::from_str("pattern6"), Some(LedPattern::Pattern6));
assert_eq!(LedPattern::from_str("6"), Some(LedPattern::Pattern6));
assert_eq!(LedPattern::from_str("PATTERN1"), Some(LedPattern::Pattern1));
assert_eq!(LedPattern::from_str("invalid"), None);
}
#[test]
fn test_led_pattern_values() {
assert_eq!(LedPattern::Off as u8, 0x0);
assert_eq!(LedPattern::On as u8, 0x1);
assert_eq!(LedPattern::Pattern1 as u8, 0x2);
assert_eq!(LedPattern::Pattern2 as u8, 0x3);
assert_eq!(LedPattern::Pattern3 as u8, 0x4);
assert_eq!(LedPattern::Pattern4 as u8, 0x5);
assert_eq!(LedPattern::Pattern5 as u8, 0x6);
assert_eq!(LedPattern::Pattern6 as u8, 0x7);
assert_eq!(LedPattern::Keep as u8, 0xF);
}
#[test]
fn test_buzzer_pattern_from_str() {
assert_eq!(BuzzerPattern::from_str("off"), Some(BuzzerPattern::Off));
assert_eq!(BuzzerPattern::from_str("on"), Some(BuzzerPattern::On));
assert_eq!(BuzzerPattern::from_str("continuous"), Some(BuzzerPattern::On));
assert_eq!(BuzzerPattern::from_str("sweep"), Some(BuzzerPattern::Sweep));
assert_eq!(BuzzerPattern::from_str("intermittent"), Some(BuzzerPattern::Intermittent));
assert_eq!(BuzzerPattern::from_str("weak"), Some(BuzzerPattern::WeakAttention));
assert_eq!(BuzzerPattern::from_str("weak-attention"), Some(BuzzerPattern::WeakAttention));
assert_eq!(BuzzerPattern::from_str("strong"), Some(BuzzerPattern::StrongAttention));
assert_eq!(BuzzerPattern::from_str("strong-attention"), Some(BuzzerPattern::StrongAttention));
assert_eq!(BuzzerPattern::from_str("shining-star"), Some(BuzzerPattern::ShiningStar));
assert_eq!(BuzzerPattern::from_str("london-bridge"), Some(BuzzerPattern::LondonBridge));
assert_eq!(BuzzerPattern::from_str("SWEEP"), Some(BuzzerPattern::Sweep));
assert_eq!(BuzzerPattern::from_str("invalid"), None);
}
#[test]
fn test_buzzer_pattern_values() {
assert_eq!(BuzzerPattern::Off as u8, 0x0);
assert_eq!(BuzzerPattern::On as u8, 0x1);
assert_eq!(BuzzerPattern::Sweep as u8, 0x2);
assert_eq!(BuzzerPattern::Intermittent as u8, 0x3);
assert_eq!(BuzzerPattern::WeakAttention as u8, 0x4);
assert_eq!(BuzzerPattern::StrongAttention as u8, 0x5);
assert_eq!(BuzzerPattern::ShiningStar as u8, 0x6);
assert_eq!(BuzzerPattern::LondonBridge as u8, 0x7);
assert_eq!(BuzzerPattern::Keep as u8, 0xF);
}
#[test]
fn test_buzzer_count_times() {
assert_eq!(BuzzerCount::times(0), None);
assert_eq!(BuzzerCount::times(1), Some(BuzzerCount(1)));
assert_eq!(BuzzerCount::times(7), Some(BuzzerCount(7)));
assert_eq!(BuzzerCount::times(14), Some(BuzzerCount(14)));
assert_eq!(BuzzerCount::times(15), None);
assert_eq!(BuzzerCount::times(100), None);
}
#[test]
fn test_buzzer_count_constants() {
assert_eq!(BuzzerCount::CONTINUOUS.value(), 0x0);
assert_eq!(BuzzerCount::KEEP.value(), 0xF);
}
#[test]
fn test_buzzer_count_value() {
let count = BuzzerCount::times(5).unwrap();
assert_eq!(count.value(), 5);
}
#[test]
fn test_buzzer_volume_level() {
assert_eq!(BuzzerVolume::level(0), Some(BuzzerVolume(0)));
assert_eq!(BuzzerVolume::level(5), Some(BuzzerVolume(5)));
assert_eq!(BuzzerVolume::level(10), Some(BuzzerVolume(10)));
assert_eq!(BuzzerVolume::level(11), None);
assert_eq!(BuzzerVolume::level(100), None);
}
#[test]
fn test_buzzer_volume_constants() {
assert_eq!(BuzzerVolume::OFF.value(), 0x0);
assert_eq!(BuzzerVolume::MAX.value(), 0xA);
assert_eq!(BuzzerVolume::KEEP.value(), 0xF);
}
#[test]
fn test_buzzer_volume_value() {
let volume = BuzzerVolume::level(7).unwrap();
assert_eq!(volume.value(), 7);
}
#[test]
fn test_setting_values() {
assert_eq!(Setting::Off as u8, 0x0);
assert_eq!(Setting::On as u8, 0x1);
}
#[test]
fn test_command_byte_generation_led() {
let color = LedColor::Red as u8;
let pattern = LedPattern::Pattern1 as u8;
let led_control = (color << 4) | pattern;
assert_eq!(led_control, 0x12);
let color = LedColor::Green as u8;
let pattern = LedPattern::On as u8;
let led_control = (color << 4) | pattern;
assert_eq!(led_control, 0x21);
let color = LedColor::Keep as u8;
let pattern = LedPattern::Keep as u8;
let led_control = (color << 4) | pattern;
assert_eq!(led_control, 0xFF);
}
#[test]
fn test_command_byte_generation_buzzer() {
let count = BuzzerCount::times(3).unwrap().value();
let pattern = BuzzerPattern::Sweep as u8;
let buzzer_control = (count << 4) | pattern;
assert_eq!(buzzer_control, 0x32);
let count = BuzzerCount::CONTINUOUS.value();
let pattern = BuzzerPattern::On as u8;
let buzzer_control = (count << 4) | pattern;
assert_eq!(buzzer_control, 0x01);
let count = BuzzerCount::KEEP.value();
let pattern = BuzzerPattern::Keep as u8;
let buzzer_control = (count << 4) | pattern;
assert_eq!(buzzer_control, 0xFF);
}
#[test]
fn test_full_command_construction() {
let buzzer_count = BuzzerCount::times(2).unwrap().value();
let buzzer_pattern = BuzzerPattern::Intermittent as u8;
let buzzer_control = (buzzer_count << 4) | buzzer_pattern;
let led_color = LedColor::Blue as u8;
let led_pattern = LedPattern::Pattern3 as u8;
let led_control = (led_color << 4) | led_pattern;
let volume = BuzzerVolume::level(5).unwrap().value();
let data = [
COMMAND_VERSION,
COMMAND_ID_CONTROL,
buzzer_control,
volume,
led_control,
0, 0, 0,
];
assert_eq!(data[0], 0x0);
assert_eq!(data[1], 0x0);
assert_eq!(data[2], 0x23);
assert_eq!(data[3], 0x5);
assert_eq!(data[4], 0x44);
}
}