#[cfg(test)]
mod tests;
use std::sync::Arc;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
channel::HidppChannel,
feature::{CreatableFeature, Feature, FeatureEndpoint},
protocol::v20::{ErrorType, Hidpp20Error},
};
pub const ZONE_PRESENCE_PAGE_LEN: usize = 14;
pub const DELTA_PACKED_LEN: usize = 15;
const TYPE_RGB_ZONE_PRESENCE: u8 = 0x00;
const MAX_INDIVIDUAL_ZONES: usize = 4;
const CONSECUTIVE_ZONES: usize = 5;
const MAX_RANGES: usize = 3;
const MAX_SINGLE_VALUE_ZONES: usize = 13;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct Rgb {
pub red: u8,
pub green: u8,
pub blue: u8,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct RgbZone {
pub zone_id: u8,
pub color: Rgb,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct RgbZoneRange {
pub first_zone_id: u8,
pub last_zone_id: u8,
pub color: Rgb,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum ZonePresencePage {
Zones0To111 = 0,
Zones112To223 = 1,
Zones224To255 = 2,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum FramePersistence {
Volatile = 0,
VolatileAndNonVolatile = 1,
}
#[derive(Clone)]
pub struct PerKeyLightingFeature {
endpoint: FeatureEndpoint,
}
impl CreatableFeature for PerKeyLightingFeature {
const ID: u16 = 0x8081;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
}
}
}
impl Feature for PerKeyLightingFeature {}
impl PerKeyLightingFeature {
pub async fn get_rgb_zone_presence(
&self,
page: ZonePresencePage,
) -> Result<[u8; ZONE_PRESENCE_PAGE_LEN], Hidpp20Error> {
let payload = self
.endpoint
.call(0, [TYPE_RGB_ZONE_PRESENCE, page.into(), 0])
.await?
.extend_payload();
let mut bitfield = [0; ZONE_PRESENCE_PAGE_LEN];
bitfield.copy_from_slice(&payload[2..2 + ZONE_PRESENCE_PAGE_LEN]);
Ok(bitfield)
}
pub async fn set_individual_rgb_zones(&self, zones: &[RgbZone]) -> Result<(), Hidpp20Error> {
validate_individual_zones(zones)?;
self.endpoint
.call_long(1, individual_zones_args(zones))
.await?;
Ok(())
}
pub async fn set_consecutive_rgb_zones(
&self,
first_zone_id: u8,
colors: [Rgb; CONSECUTIVE_ZONES],
) -> Result<(), Hidpp20Error> {
validate_zone_id(first_zone_id)?;
self.endpoint
.call_long(2, consecutive_zones_args(first_zone_id, colors))
.await?;
Ok(())
}
pub async fn set_consecutive_rgb_zones_delta_5bit(
&self,
first_zone_id: u8,
packed: [u8; DELTA_PACKED_LEN],
) -> Result<(), Hidpp20Error> {
self.send_delta(3, first_zone_id, packed).await
}
pub async fn set_consecutive_rgb_zones_delta_4bit(
&self,
first_zone_id: u8,
packed: [u8; DELTA_PACKED_LEN],
) -> Result<(), Hidpp20Error> {
self.send_delta(4, first_zone_id, packed).await
}
pub async fn set_range_rgb_zones(&self, ranges: &[RgbZoneRange]) -> Result<(), Hidpp20Error> {
validate_ranges(ranges)?;
self.endpoint.call_long(5, range_zones_args(ranges)).await?;
Ok(())
}
pub async fn set_rgb_zones_single_value(
&self,
color: Rgb,
zone_ids: &[u8],
) -> Result<(), Hidpp20Error> {
validate_single_value_zones(zone_ids)?;
self.endpoint
.call_long(6, single_value_args(color, zone_ids))
.await?;
Ok(())
}
pub async fn frame_end(
&self,
persistence: FramePersistence,
current_frame: u16,
frames_till_next_change: u16,
) -> Result<(), Hidpp20Error> {
let args = frame_end_args(persistence, current_frame, frames_till_next_change);
self.endpoint.call_long(7, args).await?;
Ok(())
}
async fn send_delta(
&self,
function: u8,
first_zone_id: u8,
packed: [u8; DELTA_PACKED_LEN],
) -> Result<(), Hidpp20Error> {
validate_zone_id(first_zone_id)?;
self.endpoint
.call_long(function, delta_args(first_zone_id, packed))
.await?;
Ok(())
}
}
fn validate_zone_id(zone_id: u8) -> Result<(), Hidpp20Error> {
if matches!(zone_id, 0 | 0xff) {
return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
}
Ok(())
}
fn validate_individual_zones(zones: &[RgbZone]) -> Result<(), Hidpp20Error> {
for zone in zones.iter().take(MAX_INDIVIDUAL_ZONES) {
validate_zone_id(zone.zone_id)?;
}
Ok(())
}
fn validate_ranges(ranges: &[RgbZoneRange]) -> Result<(), Hidpp20Error> {
for range in ranges.iter().take(MAX_RANGES) {
validate_zone_id(range.first_zone_id)?;
validate_zone_id(range.last_zone_id)?;
}
Ok(())
}
fn validate_single_value_zones(zone_ids: &[u8]) -> Result<(), Hidpp20Error> {
for &zone_id in zone_ids.iter().take(MAX_SINGLE_VALUE_ZONES) {
validate_zone_id(zone_id)?;
}
Ok(())
}
fn individual_zones_args(zones: &[RgbZone]) -> [u8; 16] {
let mut args = [0; 16];
for (slot, zone) in zones.iter().take(MAX_INDIVIDUAL_ZONES).enumerate() {
let base = slot * 4;
args[base] = zone.zone_id;
args[base + 1] = zone.color.red;
args[base + 2] = zone.color.green;
args[base + 3] = zone.color.blue;
}
args
}
fn consecutive_zones_args(first_zone_id: u8, colors: [Rgb; CONSECUTIVE_ZONES]) -> [u8; 16] {
let mut args = [0; 16];
args[0] = first_zone_id;
for (i, color) in colors.iter().enumerate() {
let base = 1 + i * 3;
args[base] = color.red;
args[base + 1] = color.green;
args[base + 2] = color.blue;
}
args
}
fn range_zones_args(ranges: &[RgbZoneRange]) -> [u8; 16] {
let mut args = [0; 16];
for (slot, range) in ranges.iter().take(MAX_RANGES).enumerate() {
let base = slot * 5;
args[base] = range.first_zone_id;
args[base + 1] = range.last_zone_id;
args[base + 2] = range.color.red;
args[base + 3] = range.color.green;
args[base + 4] = range.color.blue;
}
args
}
fn single_value_args(color: Rgb, zone_ids: &[u8]) -> [u8; 16] {
let mut args = [0; 16];
args[0] = color.red;
args[1] = color.green;
args[2] = color.blue;
for (i, &zone_id) in zone_ids.iter().take(MAX_SINGLE_VALUE_ZONES).enumerate() {
args[3 + i] = zone_id;
}
args
}
fn frame_end_args(
persistence: FramePersistence,
current_frame: u16,
frames_till_next_change: u16,
) -> [u8; 16] {
let [frame_hi, frame_lo] = current_frame.to_be_bytes();
let [next_hi, next_lo] = frames_till_next_change.to_be_bytes();
let mut args = [0; 16];
args[..5].copy_from_slice(&[persistence.into(), frame_hi, frame_lo, next_hi, next_lo]);
args
}
fn delta_args(first_zone_id: u8, packed: [u8; DELTA_PACKED_LEN]) -> [u8; 16] {
let mut args = [0; 16];
args[0] = first_zone_id;
args[1..1 + DELTA_PACKED_LEN].copy_from_slice(&packed);
args
}