pub mod event;
pub mod types;
#[cfg(test)]
mod tests;
use openlogi_hidpp_derive::Feature;
pub use event::ColorLedEffectsEvent;
pub use types::{
ColorLedInfo, CyclingDirection, EffectId, EffectSettings, ExtCapabilities, LedBinIndex,
LedBinInfo, LocationEffect, NvCapabilities, NvCapabilityState, NvConfig, Persistence,
PersistenceSource, PersistencyCapabilities, Rgb, SwControl, SwControlState,
ZONE_EFFECT_PARAM_COUNT, ZoneEffect, ZoneEffectInfo, ZoneInfo,
};
use self::types::be16;
use crate::{
feature::{EventSource, FeatureEndpoint},
protocol::v20::{ErrorType, Hidpp20Error},
};
#[derive(Feature)]
#[creatable(id = 0x8070, version = 0)]
pub struct ColorLedEffectsFeature {
endpoint: FeatureEndpoint,
events: EventSource<ColorLedEffectsEvent>,
}
impl ColorLedEffectsFeature {
pub async fn get_info(&self) -> Result<ColorLedInfo, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(ColorLedInfo::from_payload(&payload))
}
pub async fn get_zone_info(&self, zone_index: u8) -> Result<ZoneInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(1, [zone_index, 0, 0])
.await?
.extend_payload();
ZoneInfo::from_payload(&payload)
}
pub async fn get_zone_effect_info(
&self,
zone_index: u8,
zone_effect_index: u8,
) -> Result<ZoneEffectInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(2, [zone_index, zone_effect_index, 0])
.await?
.extend_payload();
ZoneEffectInfo::from_payload(&payload)
}
pub async fn set_zone_effect(
&self,
zone_index: u8,
zone_effect_index: u8,
params: [u8; ZONE_EFFECT_PARAM_COUNT],
persistence: Persistence,
) -> Result<(), Hidpp20Error> {
let mut args = [0; 16];
args[0] = zone_index;
args[1] = zone_effect_index;
args[2..2 + ZONE_EFFECT_PARAM_COUNT].copy_from_slice(¶ms);
args[12] = persistence.into();
self.endpoint.call_long(3, args).await?;
Ok(())
}
pub async fn get_nv_config(
&self,
capability: NvCapabilities,
) -> Result<NvConfig, Hidpp20Error> {
validate_single_nv_capability(capability)?;
let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
let payload = self
.endpoint
.call(4, [cap_hi, cap_lo, 0])
.await?
.extend_payload();
Ok(NvConfig {
capability: NvCapabilities::from_bits_retain(be16(&payload, 0)),
state: NvCapabilityState::try_from(payload[2])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
param1: payload[3],
param2: payload[4],
})
}
pub async fn set_nv_config(
&self,
capability: NvCapabilities,
state: NvCapabilityState,
param1: u8,
param2: u8,
) -> Result<(), Hidpp20Error> {
validate_single_nv_capability(capability)?;
let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
let mut args = [0; 16];
args[..5].copy_from_slice(&[cap_hi, cap_lo, state.into(), param1, param2]);
self.endpoint.call_long(5, args).await?;
Ok(())
}
pub async fn get_led_bin_info(
&self,
zone_index: u8,
led_bin_index: LedBinIndex,
) -> Result<LedBinInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(6, [zone_index, led_bin_index.into(), 0])
.await?
.extend_payload();
LedBinInfo::from_payload(&payload)
}
pub async fn get_sw_control(&self) -> Result<SwControlState, Hidpp20Error> {
let payload = self.endpoint.call(7, [0; 3]).await?.extend_payload();
Ok(SwControlState {
control: SwControl::try_from(payload[0])
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
sync_events: payload[1] != 0,
})
}
pub async fn set_sw_control(
&self,
control: SwControl,
sync_events: bool,
) -> Result<(), Hidpp20Error> {
self.endpoint
.call(8, [control.into(), u8::from(sync_events), 0])
.await?;
Ok(())
}
pub async fn get_effect_settings(
&self,
zone_index: u8,
source: PersistenceSource,
) -> Result<EffectSettings, Hidpp20Error> {
let payload = self
.endpoint
.call(9, [zone_index, source.into(), 0])
.await?
.extend_payload();
Ok(EffectSettings::from_payload(&payload))
}
pub async fn clear_effect_settings(&self, zone_index: u8) -> Result<(), Hidpp20Error> {
self.endpoint.call(10, [zone_index, 0, 0]).await?;
Ok(())
}
pub async fn set_cycling_direction(
&self,
direction: CyclingDirection,
) -> Result<(), Hidpp20Error> {
self.endpoint.call(11, [direction.into(), 0, 0]).await?;
Ok(())
}
pub async fn get_current_color(&self, zone_index: u8) -> Result<Rgb, Hidpp20Error> {
let payload = self
.endpoint
.call(12, [zone_index, 0, 0])
.await?
.extend_payload();
Ok(Rgb {
red: payload[1],
green: payload[2],
blue: payload[3],
})
}
pub async fn synchronize_effect(
&self,
zone_index: u8,
drift_value: i16,
) -> Result<(), Hidpp20Error> {
let [drift_hi, drift_lo] = drift_value.to_be_bytes();
let mut args = [0; 16];
args[..4].copy_from_slice(&[zone_index, 0, drift_hi, drift_lo]);
self.endpoint.call_long(13, args).await?;
Ok(())
}
pub async fn get_zone_effect(
&self,
zone_index: u8,
source: PersistenceSource,
) -> Result<ZoneEffect, Hidpp20Error> {
let payload = self
.endpoint
.call(14, [zone_index, source.into(), 0])
.await?
.extend_payload();
Ok(ZoneEffect::from_payload(&payload))
}
pub async fn set_led_bin_info(&self, info: &LedBinInfo) -> Result<LedBinInfo, Hidpp20Error> {
let mut args = [0; 16];
args[0] = info.zone_index;
args[1] = info.led_bin_index.into();
args[2..4].copy_from_slice(&info.red.to_be_bytes());
args[4..6].copy_from_slice(&info.green.to_be_bytes());
args[6..8].copy_from_slice(&info.blue.to_be_bytes());
args[8..10].copy_from_slice(&info.white.to_be_bytes());
let payload = self.endpoint.call_long(15, args).await?.extend_payload();
LedBinInfo::from_payload(&payload)
}
}
fn validate_single_nv_capability(capability: NvCapabilities) -> Result<(), Hidpp20Error> {
if capability.bits().count_ones() != 1 {
return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
}
Ok(())
}