diff --git a/.github/workflows/build_and_release.yml b/.github/workflows/build_and_release.yml index 5474259..8ab9376 100644 --- a/.github/workflows/build_and_release.yml +++ b/.github/workflows/build_and_release.yml @@ -55,7 +55,31 @@ jobs: with: path: litra_${{ steps.sanitise_ref.outputs.value }}_${{ matrix.job.binary_name }} name: litra_${{ steps.sanitise_ref.outputs.value }}_${{ matrix.job.binary_name }} - release: + cargo_publish_dry_run: + name: Publish with Cargo in dry-run mode + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - name: Install libudev-dev + run: sudo apt-get update && sudo apt-get install libudev-dev + - name: Cache Rust dependencies + uses: actions/cache@v4.0.0 + with: + path: target + key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.OS }}-build- + - name: Install latest Rust nightly + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: rustc, cargo + toolchain: nightly + override: true + - name: Publish to Crates.io + run: cargo publish --dry-run + create_github_release: name: Create release with binary assets runs-on: ubuntu-latest needs: build @@ -83,7 +107,7 @@ jobs: publish_on_homebrew: name: Publish release on Homebrew runs-on: ubuntu-latest - needs: release + needs: create_github_release if: startsWith(github.event.ref, 'refs/tags/v') steps: - name: Get released version @@ -97,4 +121,31 @@ jobs: push-to: timrogers/homebrew-tap create-pullrequest: true env: - COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }} \ No newline at end of file + COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }} + cargo_publish: + name: Publish with Cargo to Crates.io + runs-on: ubuntu-latest + needs: + - create_github_release + - cargo_publish_dry_run + if: startsWith(github.event.ref, 'refs/tags/v') + steps: + - uses: actions/checkout@v4 + - name: Install libudev-dev + run: sudo apt-get update && sudo apt-get install libudev-dev + - name: Cache Rust dependencies + uses: actions/cache@v4.0.0 + with: + path: target + key: ${{ runner.OS }}-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.OS }}-build- + - name: Install latest Rust nightly + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: rustc, cargo + toolchain: nightly + override: true + - name: Publish to Crates.io + run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index f70b82a..46584be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,13 @@ name = "litra" version = "0.2.1" edition = "2021" +authors = ["Tim Rogers "] +description = "Control your Logitech Litra light from the command line" +repository = "https://github.com/timrogers/litra-rs" +license = "MIT" +readme = "README.md" +categories = ["hardware-support", "command-line-utilities"] +keywords = ["logitech", "litra", "glow", "beam", "light"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ddbe142 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,424 @@ +use hidapi::{HidApi, HidDevice}; +use serde::Serialize; +use std::fmt; + +#[derive(Debug, Serialize)] +pub enum DeviceType { + #[serde(rename = "Litra Glow")] + LitraGlow, + #[serde(rename = "Litra Beam")] + LitraBeam, + #[serde(rename = "Litra Beam LX")] + LitraBeamLX, +} + +impl fmt::Display for DeviceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DeviceType::LitraGlow => write!(f, "Litra Glow"), + DeviceType::LitraBeam => write!(f, "Litra Beam"), + DeviceType::LitraBeamLX => write!(f, "Litra Beam LX"), + } + } +} + +#[derive(Serialize, Debug)] +pub struct Device { + pub serial_number: String, + pub device_type: DeviceType, + pub is_on: bool, + pub brightness_in_lumen: u16, + pub temperature_in_kelvin: u16, + #[serde(skip_serializing)] + pub device_handle: HidDevice, + pub minimum_brightness_in_lumen: u16, + pub maximum_brightness_in_lumen: u16, + pub minimum_temperature_in_kelvin: u16, + pub maximum_temperature_in_kelvin: u16, +} + +const VENDOR_ID: u16 = 0x046d; +const PRODUCT_IDS: [u16; 4] = [0xc900, 0xc901, 0xb901, 0xc903]; +const USAGE_PAGE: u16 = 0xff43; + +fn get_device_type(product_id: u16) -> DeviceType { + match product_id { + 0xc900 => DeviceType::LitraGlow, + 0xc901 => DeviceType::LitraBeam, + 0xb901 => DeviceType::LitraBeam, + 0xc903 => DeviceType::LitraBeamLX, + _ => panic!("Unknown product ID"), + } +} + +fn get_minimum_brightness_in_lumen(device_type: &DeviceType) -> u16 { + match device_type { + DeviceType::LitraGlow => 20, + DeviceType::LitraBeam => 30, + DeviceType::LitraBeamLX => 30, + } +} + +fn get_maximum_brightness_in_lumen(device_type: &DeviceType) -> u16 { + match device_type { + DeviceType::LitraGlow => 250, + DeviceType::LitraBeam => 400, + DeviceType::LitraBeamLX => 400, + } +} + +const MINIMUM_TEMPERATURE_IN_KELVIN: u16 = 2700; +const MAXIMUM_TEMPERATURE_IN_KELVIN: u16 = 6500; + +pub fn get_connected_devices(api: HidApi, serial_number: Option) -> Vec { + let hid_devices = api.device_list(); + + let mut litra_devices = Vec::new(); + + for device in hid_devices { + if device.vendor_id() == VENDOR_ID + && PRODUCT_IDS.contains(&device.product_id()) + && device.usage_page() == USAGE_PAGE + { + if let Some(serial_number) = &serial_number { + if device.serial_number().unwrap_or("") != *serial_number { + continue; + } + } + + litra_devices.push(device); + } + } + + return litra_devices + .iter() + .map(|device| { + let device_type = get_device_type(device.product_id()); + let device_handle = api.open_path(device.path()).unwrap(); + let is_on = is_on(&device_handle, &device_type); + let brightness_in_lumen = get_brightness_in_lumen(&device_handle, &device_type); + let temperature_in_kelvin = get_temperature_in_kelvin(&device_handle, &device_type); + let minimum_brightness_in_lumen = get_minimum_brightness_in_lumen(&device_type); + let maximum_brightness_in_lumen = get_maximum_brightness_in_lumen(&device_type); + + Device { + serial_number: device.serial_number().unwrap_or("").to_string(), + device_type: device_type, + is_on: is_on, + brightness_in_lumen: brightness_in_lumen, + temperature_in_kelvin: temperature_in_kelvin, + device_handle: device_handle, + minimum_brightness_in_lumen: minimum_brightness_in_lumen, + maximum_brightness_in_lumen: maximum_brightness_in_lumen, + minimum_temperature_in_kelvin: MINIMUM_TEMPERATURE_IN_KELVIN, + maximum_temperature_in_kelvin: MAXIMUM_TEMPERATURE_IN_KELVIN, + } + }) + .collect(); +} + +fn generate_is_on_bytes(device_type: &DeviceType) -> [u8; 20] { + match device_type { + DeviceType::LitraGlow => [ + 0x11, 0xff, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, 0xff, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, 0xff, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + } +} + +pub fn is_on(device_handle: &HidDevice, device_type: &DeviceType) -> bool { + let message = generate_is_on_bytes(device_type); + + device_handle.write(&message).unwrap(); + + let mut response_buffer = [0x00; 20]; + let response = device_handle.read(&mut response_buffer[..]).unwrap(); + + return response_buffer[..response][4] == 1; +} + +fn generate_get_brightness_in_lumen_bytes(device_type: &DeviceType) -> [u8; 20] { + match device_type { + DeviceType::LitraGlow => [ + 0x11, 0xff, 0x04, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, 0xff, 0x04, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, 0xff, 0x06, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + } +} + +pub fn get_brightness_in_lumen(device_handle: &HidDevice, device_type: &DeviceType) -> u16 { + let message = generate_get_brightness_in_lumen_bytes(device_type); + + device_handle.write(&message).unwrap(); + + let mut response_buffer = [0x00; 20]; + let response = device_handle.read(&mut response_buffer[..]).unwrap(); + + return response_buffer[..response][5].into(); +} + +fn generate_get_temperature_in_kelvin_bytes(device_type: &DeviceType) -> [u8; 20] { + match device_type { + DeviceType::LitraGlow => [ + 0x11, 0xff, 0x04, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, 0xff, 0x04, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, 0xff, 0x06, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + } +} + +pub fn get_temperature_in_kelvin(device_handle: &HidDevice, device_type: &DeviceType) -> u16 { + let message = generate_get_temperature_in_kelvin_bytes(device_type); + + device_handle.write(&message).unwrap(); + + let mut response_buffer = [0x00; 20]; + let response = device_handle.read(&mut response_buffer[..]).unwrap(); + return (response_buffer[..response][4] as u16 * 256 + response_buffer[..response][5] as u16) + .into(); +} + +fn generate_turn_on_bytes(device_type: &DeviceType) -> [u8; 20] { + match device_type { + DeviceType::LitraGlow => [ + 0x11, 0xff, 0x04, 0x1c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, 0xff, 0x04, 0x1c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, 0xff, 0x06, 0x1c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + } +} + +pub fn turn_on(device_handle: &HidDevice, device_type: &DeviceType) { + let message = generate_turn_on_bytes(device_type); + + device_handle.write(&message).unwrap(); +} + +fn generate_turn_off_bytes(device_type: &DeviceType) -> [u8; 20] { + match device_type { + DeviceType::LitraGlow => [ + 0x11, 0xff, 0x04, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, 0xff, 0x04, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, 0xff, 0x06, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ], + } +} + +pub fn turn_off(device_handle: &HidDevice, device_type: &DeviceType) { + let message = generate_turn_off_bytes(device_type); + + device_handle.write(&message).unwrap(); +} + +fn integer_to_bytes(integer: u16) -> [u8; 2] { + return [(integer / 256) as u8, (integer % 256) as u8]; +} + +fn generate_set_brightness_in_lumen_bytes( + device_type: &DeviceType, + brightness_in_lumen: u16, +) -> [u8; 20] { + let brightness_bytes = integer_to_bytes(brightness_in_lumen); + + match device_type { + DeviceType::LitraGlow => [ + 0x11, + 0xff, + 0x04, + 0x4c, + brightness_bytes[0], + brightness_bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, + 0xff, + 0x04, + 0x4c, + brightness_bytes[0], + brightness_bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, + 0xff, + 0x06, + 0x4c, + brightness_bytes[0], + brightness_bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ], + } +} + +pub fn set_brightness_in_lumen( + device_handle: &HidDevice, + device_type: &DeviceType, + brightness_in_lumen: u16, +) { + let message = generate_set_brightness_in_lumen_bytes(device_type, brightness_in_lumen); + + device_handle.write(&message).unwrap(); +} + +fn generate_set_temperature_in_kelvin_bytes( + device_type: &DeviceType, + temperature_in_kelvin: u16, +) -> [u8; 20] { + let temperature_bytes = integer_to_bytes(temperature_in_kelvin); + + match device_type { + DeviceType::LitraGlow => [ + 0x11, + 0xff, + 0x04, + 0x9c, + temperature_bytes[0], + temperature_bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ], + DeviceType::LitraBeam => [ + 0x11, + 0xff, + 0x04, + 0x9c, + temperature_bytes[0], + temperature_bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ], + DeviceType::LitraBeamLX => [ + 0x11, + 0xff, + 0x06, + 0x9c, + temperature_bytes[0], + temperature_bytes[1], + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + ], + } +} + +pub fn set_temperature_in_kelvin( + device_handle: &HidDevice, + device_type: &DeviceType, + temperature_in_kelvin: u16, +) { + let message = generate_set_temperature_in_kelvin_bytes(device_type, temperature_in_kelvin); + + device_handle.write(&message).unwrap(); +} diff --git a/src/main.rs b/src/main.rs index 051d7b4..2c58bbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use clap::{ArgGroup, Parser, Subcommand}; -use hidapi::{HidApi, HidDevice}; -use serde::Serialize; -use std::fmt; +use litra::{ + get_connected_devices, set_brightness_in_lumen, set_temperature_in_kelvin, turn_off, turn_on, +}; /// Control your USB-connected Logitech Litra lights from the command line #[derive(Debug, Parser)] @@ -67,255 +67,10 @@ enum Commands { }, } -#[derive(Debug, Serialize)] -pub enum DeviceType { - #[serde(rename = "Litra Glow")] - LitraGlow, - #[serde(rename = "Litra Beam")] - LitraBeam, - #[serde(rename = "Litra Beam LX")] - LitraBeamLX, -} - -impl fmt::Display for DeviceType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DeviceType::LitraGlow => write!(f, "Litra Glow"), - DeviceType::LitraBeam => write!(f, "Litra Beam"), - DeviceType::LitraBeamLX => write!(f, "Litra Beam LX"), - } - } -} - -#[derive(Serialize, Debug)] -struct Device { - serial_number: String, - device_type: DeviceType, - is_on: bool, - brightness_in_lumen: u16, - temperature_in_kelvin: u16, - #[serde(skip_serializing)] - device_handle: HidDevice, - minimum_brightness_in_lumen: u16, - maximum_brightness_in_lumen: u16, - minimum_temperature_in_kelvin: u16, - maximum_temperature_in_kelvin: u16, -} - -const VENDOR_ID: u16 = 0x046d; -const PRODUCT_IDS: [u16; 4] = [0xc900, 0xc901, 0xb901, 0xc903]; -const USAGE_PAGE: u16 = 0xff43; - -fn get_device_type(product_id: u16) -> DeviceType { - match product_id { - 0xc900 => DeviceType::LitraGlow, - 0xc901 => DeviceType::LitraBeam, - 0xb901 => DeviceType::LitraBeam, - 0xc903 => DeviceType::LitraBeamLX, - _ => panic!("Unknown product ID"), - } -} - -fn get_minimum_brightness_in_lumen(device_type: &DeviceType) -> u16 { - match device_type { - DeviceType::LitraGlow => 20, - DeviceType::LitraBeam => 30, - DeviceType::LitraBeamLX => 30, - } -} - -fn get_maximum_brightness_in_lumen(device_type: &DeviceType) -> u16 { - match device_type { - DeviceType::LitraGlow => 250, - DeviceType::LitraBeam => 400, - DeviceType::LitraBeamLX => 400, - } -} - -fn multiples_within_range(multiples_of: u16, start_range: u16, end_range: u16) -> Vec { - (start_range..=end_range) - .filter(|n| n % multiples_of == 0) - .collect() -} - -const MINIMUM_TEMPERATURE_IN_KELVIN: u16 = 2700; -const MAXIMUM_TEMPERATURE_IN_KELVIN: u16 = 6500; - -fn get_connected_devices(api: HidApi, serial_number: Option) -> Vec { - let hid_devices = api.device_list(); - - let mut litra_devices = Vec::new(); - - for device in hid_devices { - if device.vendor_id() == VENDOR_ID - && PRODUCT_IDS.contains(&device.product_id()) - && device.usage_page() == USAGE_PAGE - { - if let Some(serial_number) = &serial_number { - if device.serial_number().unwrap_or("") != *serial_number { - continue; - } - } - - litra_devices.push(device); - } - } - - return litra_devices - .iter() - .map(|device| { - let device_type = get_device_type(device.product_id()); - let device_handle = api.open_path(device.path()).unwrap(); - let is_on = is_on(&device_handle, &device_type); - let brightness_in_lumen = get_brightness_in_lumen(&device_handle, &device_type); - let temperature_in_kelvin = get_temperature_in_kelvin(&device_handle, &device_type); - let minimum_brightness_in_lumen = get_minimum_brightness_in_lumen(&device_type); - let maximum_brightness_in_lumen = get_maximum_brightness_in_lumen(&device_type); - - Device { - serial_number: device.serial_number().unwrap_or("").to_string(), - device_type: device_type, - is_on: is_on, - brightness_in_lumen: brightness_in_lumen, - temperature_in_kelvin: temperature_in_kelvin, - device_handle: device_handle, - minimum_brightness_in_lumen: minimum_brightness_in_lumen, - maximum_brightness_in_lumen: maximum_brightness_in_lumen, - minimum_temperature_in_kelvin: MINIMUM_TEMPERATURE_IN_KELVIN, - maximum_temperature_in_kelvin: MAXIMUM_TEMPERATURE_IN_KELVIN, - } - }) - .collect(); -} - -fn generate_is_on_bytes(device_type: &DeviceType) -> [u8; 20] { - match device_type { - DeviceType::LitraGlow => [ - 0x11, 0xff, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, 0xff, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, 0xff, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - } -} - -fn is_on(device_handle: &HidDevice, device_type: &DeviceType) -> bool { - let message = generate_is_on_bytes(device_type); - - device_handle.write(&message).unwrap(); - - let mut response_buffer = [0x00; 20]; - let response = device_handle.read(&mut response_buffer[..]).unwrap(); - - return response_buffer[..response][4] == 1; -} - -fn generate_get_brightness_in_lumen_bytes(device_type: &DeviceType) -> [u8; 20] { - match device_type { - DeviceType::LitraGlow => [ - 0x11, 0xff, 0x04, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, 0xff, 0x04, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, 0xff, 0x06, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - } -} - -fn get_brightness_in_lumen(device_handle: &HidDevice, device_type: &DeviceType) -> u16 { - let message = generate_get_brightness_in_lumen_bytes(device_type); - - device_handle.write(&message).unwrap(); - - let mut response_buffer = [0x00; 20]; - let response = device_handle.read(&mut response_buffer[..]).unwrap(); - - return response_buffer[..response][5].into(); -} - -fn generate_get_temperature_in_kelvin_bytes(device_type: &DeviceType) -> [u8; 20] { - match device_type { - DeviceType::LitraGlow => [ - 0x11, 0xff, 0x04, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, 0xff, 0x04, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, 0xff, 0x06, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - } -} - -fn get_temperature_in_kelvin(device_handle: &HidDevice, device_type: &DeviceType) -> u16 { - let message = generate_get_temperature_in_kelvin_bytes(device_type); - - device_handle.write(&message).unwrap(); - - let mut response_buffer = [0x00; 20]; - let response = device_handle.read(&mut response_buffer[..]).unwrap(); - return (response_buffer[..response][4] as u16 * 256 + response_buffer[..response][5] as u16) - .into(); -} - -fn generate_turn_on_bytes(device_type: &DeviceType) -> [u8; 20] { - match device_type { - DeviceType::LitraGlow => [ - 0x11, 0xff, 0x04, 0x1c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, 0xff, 0x04, 0x1c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, 0xff, 0x06, 0x1c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - } -} - -fn turn_on(device_handle: &HidDevice, device_type: &DeviceType) { - let message = generate_turn_on_bytes(device_type); - - device_handle.write(&message).unwrap(); -} - -fn generate_turn_off_bytes(device_type: &DeviceType) -> [u8; 20] { - match device_type { - DeviceType::LitraGlow => [ - 0x11, 0xff, 0x04, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, 0xff, 0x04, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, 0xff, 0x06, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ], - } -} - -fn turn_off(device_handle: &HidDevice, device_type: &DeviceType) { - let message = generate_turn_off_bytes(device_type); - - device_handle.write(&message).unwrap(); +fn percentage_within_range(percentage: u32, start_range: u32, end_range: u32) -> u32 { + let result = ((percentage - 1) as f64 / (100 - 1) as f64) * (end_range - start_range) as f64 + + start_range as f64; + result.round() as u32 } fn get_is_on_text(is_on: bool) -> &'static str { @@ -334,186 +89,10 @@ fn get_is_on_emoji(is_on: bool) -> &'static str { return "🌑"; } -fn integer_to_bytes(integer: u16) -> [u8; 2] { - return [(integer / 256) as u8, (integer % 256) as u8]; -} - -fn generate_set_brightness_in_lumen_bytes( - device_type: &DeviceType, - brightness_in_lumen: u16, -) -> [u8; 20] { - let brightness_bytes = integer_to_bytes(brightness_in_lumen); - - match device_type { - DeviceType::LitraGlow => [ - 0x11, - 0xff, - 0x04, - 0x4c, - brightness_bytes[0], - brightness_bytes[1], - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, - 0xff, - 0x04, - 0x4c, - brightness_bytes[0], - brightness_bytes[1], - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, - 0xff, - 0x06, - 0x4c, - brightness_bytes[0], - brightness_bytes[1], - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - } -} - -fn set_brightness_in_lumen( - device_handle: &HidDevice, - device_type: &DeviceType, - brightness_in_lumen: u16, -) { - let message = generate_set_brightness_in_lumen_bytes(device_type, brightness_in_lumen); - - device_handle.write(&message).unwrap(); -} - -fn generate_set_temperature_in_kelvin_bytes( - device_type: &DeviceType, - temperature_in_kelvin: u16, -) -> [u8; 20] { - let temperature_bytes = integer_to_bytes(temperature_in_kelvin); - - match device_type { - DeviceType::LitraGlow => [ - 0x11, - 0xff, - 0x04, - 0x9c, - temperature_bytes[0], - temperature_bytes[1], - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - DeviceType::LitraBeam => [ - 0x11, - 0xff, - 0x04, - 0x9c, - temperature_bytes[0], - temperature_bytes[1], - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - DeviceType::LitraBeamLX => [ - 0x11, - 0xff, - 0x06, - 0x9c, - temperature_bytes[0], - temperature_bytes[1], - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - 0x00, - ], - } -} - -fn set_temperature_in_kelvin( - device_handle: &HidDevice, - device_type: &DeviceType, - temperature_in_kelvin: u16, -) { - let message = generate_set_temperature_in_kelvin_bytes(device_type, temperature_in_kelvin); - - device_handle.write(&message).unwrap(); -} - -fn percentage_within_range(percentage: u32, start_range: u32, end_range: u32) -> u32 { - let result = ((percentage - 1) as f64 / (100 - 1) as f64) * (end_range - start_range) as f64 - + start_range as f64; - result.round() as u32 +fn multiples_within_range(multiples_of: u16, start_range: u16, end_range: u16) -> Vec { + (start_range..=end_range) + .filter(|n| n % multiples_of == 0) + .collect() } fn main() {