pub mod event;
pub mod types;
#[cfg(test)]
mod tests;
use std::sync::Arc;
pub use event::RgbEffectsEvent;
pub use types::{
ActivityEventType, CLUSTER_EFFECT_PARAM_COUNT, DisplayPersistencyCapabilities,
EventsNotificationFlags, LED_BIN_PARAM_COUNT, LedBinIndex, ONBOARD_INFO_PARAM_COUNT,
PowerModeTarget, RgbClusterInfo, RgbDeviceInfo, RgbEffectInfo, RgbExtCapabilities,
RgbNvCapabilities, RgbNvConfig, RgbPersistence, RgbPowerMode, RgbPowerModeConfig, RgbSwControl,
SlotInfoType, SwControlFlags,
};
use self::types::{ALL_CLUSTERS, ALL_EFFECTS, GetOrSet, be16};
use crate::{
channel::{HidppChannel, MessageListenerGuard},
event::EventEmitter,
feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
protocol::v20::Hidpp20Error,
};
const TYPE_GENERAL_INFO: u8 = 0x00;
const TYPE_ONBOARD_EFFECT: u8 = 0x01;
const GET_BACKUP: u8 = 0x02;
const POWER_TARGET_SHIFT: u8 = 2;
pub struct RgbEffectsFeature {
endpoint: FeatureEndpoint,
emitter: Arc<EventEmitter<RgbEffectsEvent>>,
_msg_listener: MessageListenerGuard,
}
impl CreatableFeature for RgbEffectsFeature {
const ID: u16 = 0x8071;
const STARTING_VERSION: u8 = 0;
fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
let emitter = Arc::new(EventEmitter::new());
let listener = chan.add_msg_listener_guarded({
let emitter = Arc::clone(&emitter);
move |raw, matched| {
let Some((func, payload)) =
event_payload(raw, matched, device_index, feature_index)
else {
return;
};
if let Some(event) = event::decode_event(func.to_lo(), &payload) {
emitter.emit(event);
}
}
});
Self {
endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
emitter,
_msg_listener: listener,
}
}
}
impl Feature for RgbEffectsFeature {}
impl EmittingFeature<RgbEffectsEvent> for RgbEffectsFeature {
fn listen(&self) -> async_channel::Receiver<RgbEffectsEvent> {
self.emitter.create_receiver()
}
}
impl RgbEffectsFeature {
pub async fn get_device_info(&self) -> Result<RgbDeviceInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(0, [ALL_CLUSTERS, ALL_EFFECTS, TYPE_GENERAL_INFO])
.await?
.extend_payload();
Ok(RgbDeviceInfo::from_payload(&payload))
}
pub async fn get_cluster_info(
&self,
cluster_index: u8,
) -> Result<RgbClusterInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(0, [cluster_index, ALL_EFFECTS, TYPE_GENERAL_INFO])
.await?
.extend_payload();
Ok(RgbClusterInfo::from_payload(&payload))
}
pub async fn get_effect_info(
&self,
cluster_index: u8,
cluster_effect_index: u8,
) -> Result<RgbEffectInfo, Hidpp20Error> {
let payload = self
.endpoint
.call(0, [cluster_index, cluster_effect_index, TYPE_GENERAL_INFO])
.await?
.extend_payload();
Ok(RgbEffectInfo::from_payload(&payload))
}
pub async fn get_onboard_effect_info(
&self,
cluster_index: u8,
cluster_effect_index: u8,
slot: u8,
slot_info_type: SlotInfoType,
) -> Result<[u8; ONBOARD_INFO_PARAM_COUNT], Hidpp20Error> {
let mut args = [0; 16];
args[..5].copy_from_slice(&[
cluster_index,
cluster_effect_index,
TYPE_ONBOARD_EFFECT,
slot,
slot_info_type.into(),
]);
let payload = self.endpoint.call_long(0, args).await?.extend_payload();
let mut params = [0; ONBOARD_INFO_PARAM_COUNT];
params.copy_from_slice(&payload[3..3 + ONBOARD_INFO_PARAM_COUNT]);
Ok(params)
}
pub async fn set_rgb_cluster_effect(
&self,
cluster_index: u8,
cluster_effect_index: u8,
params: [u8; CLUSTER_EFFECT_PARAM_COUNT],
persistence: RgbPersistence,
power_mode: PowerModeTarget,
) -> Result<(), Hidpp20Error> {
let mut args = [0; 16];
args[0] = cluster_index;
args[1] = cluster_effect_index;
args[2..2 + CLUSTER_EFFECT_PARAM_COUNT].copy_from_slice(¶ms);
args[12] = persistence.bits() | (u8::from(power_mode) << POWER_TARGET_SHIFT);
self.endpoint.call_long(1, args).await?;
Ok(())
}
pub async fn set_multi_led_cluster_pattern(
&self,
cluster_index: u8,
pattern: u8,
) -> Result<(), Hidpp20Error> {
self.endpoint.call(2, [cluster_index, pattern, 0]).await?;
Ok(())
}
pub async fn get_nv_config(
&self,
capability: RgbNvCapabilities,
) -> Result<RgbNvConfig, Hidpp20Error> {
let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
let payload = self
.endpoint
.call(3, [GetOrSet::Get.into(), cap_hi, cap_lo])
.await?
.extend_payload();
Ok(RgbNvConfig {
capability: RgbNvCapabilities::from_bits_retain(be16(&payload, 1)),
state: payload[3],
param1: payload[4],
param2: payload[5],
})
}
pub async fn set_nv_config(
&self,
capability: RgbNvCapabilities,
state: u8,
param1: u8,
param2: u8,
) -> Result<(), Hidpp20Error> {
let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
let mut args = [0; 16];
args[..6].copy_from_slice(&[GetOrSet::Set.into(), cap_hi, cap_lo, state, param1, param2]);
self.endpoint.call_long(3, args).await?;
Ok(())
}
pub async fn get_led_bin_info(
&self,
cluster_index: u8,
led_bin_index: LedBinIndex,
backup: bool,
) -> Result<[u8; LED_BIN_PARAM_COUNT], Hidpp20Error> {
let get_or_set = if backup {
GET_BACKUP
} else {
GetOrSet::Get.into()
};
let payload = self
.endpoint
.call(4, [get_or_set, cluster_index, led_bin_index.into()])
.await?
.extend_payload();
let mut params = [0; LED_BIN_PARAM_COUNT];
params.copy_from_slice(&payload[3..3 + LED_BIN_PARAM_COUNT]);
Ok(params)
}
pub async fn set_led_bin_info(
&self,
cluster_index: u8,
led_bin_index: LedBinIndex,
params: [u8; LED_BIN_PARAM_COUNT],
) -> Result<(), Hidpp20Error> {
let mut args = [0; 16];
args[0] = GetOrSet::Set.into();
args[1] = cluster_index;
args[2] = led_bin_index.into();
args[3..3 + LED_BIN_PARAM_COUNT].copy_from_slice(¶ms);
self.endpoint.call_long(4, args).await?;
Ok(())
}
pub async fn get_sw_control(&self) -> Result<RgbSwControl, Hidpp20Error> {
let payload = self
.endpoint
.call(5, [GetOrSet::Get.into(), 0, 0])
.await?
.extend_payload();
Ok(RgbSwControl {
control: SwControlFlags::from_bits_retain(payload[1]),
events: EventsNotificationFlags::from_bits_retain(payload[2]),
})
}
pub async fn set_sw_control(
&self,
control: SwControlFlags,
events: EventsNotificationFlags,
) -> Result<(), Hidpp20Error> {
self.endpoint
.call(5, [GetOrSet::Set.into(), control.bits(), events.bits()])
.await?;
Ok(())
}
pub async fn set_effect_sync_correction(
&self,
cluster_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(&[cluster_index, 0, drift_hi, drift_lo]);
self.endpoint.call_long(6, args).await?;
Ok(())
}
pub async fn get_power_mode_config(&self) -> Result<RgbPowerModeConfig, Hidpp20Error> {
let payload = self
.endpoint
.call(7, [GetOrSet::Get.into(), 0, 0])
.await?
.extend_payload();
Ok(RgbPowerModeConfig::from_payload(&payload))
}
pub async fn set_power_mode_config(
&self,
config: RgbPowerModeConfig,
) -> Result<(), Hidpp20Error> {
let [flags_hi, flags_lo] = config.flags.to_be_bytes();
let [psave_hi, psave_lo] = config.no_activity_timeout_to_power_save.to_be_bytes();
let [off_hi, off_lo] = config.no_activity_timeout_to_off.to_be_bytes();
let mut args = [0; 16];
args[..7].copy_from_slice(&[
GetOrSet::Set.into(),
flags_hi,
flags_lo,
psave_hi,
psave_lo,
off_hi,
off_lo,
]);
self.endpoint.call_long(7, args).await?;
Ok(())
}
pub async fn get_power_mode(&self) -> Result<RgbPowerMode, Hidpp20Error> {
let payload = self
.endpoint
.call(8, [GetOrSet::Get.into(), 0, 0])
.await?
.extend_payload();
RgbPowerMode::try_from(payload[1]).map_err(|_| Hidpp20Error::UnsupportedResponse)
}
pub async fn set_power_mode(&self, mode: RgbPowerMode) -> Result<(), Hidpp20Error> {
self.endpoint
.call(8, [GetOrSet::Set.into(), mode.into(), 0])
.await?;
Ok(())
}
pub async fn shutdown(&self) -> Result<(), Hidpp20Error> {
self.endpoint.call(9, [0; 3]).await?;
Ok(())
}
}