use std::sync::Arc;
use hidpp::channel::{ChannelError, HidppChannel};
use hidpp::device::Device;
use hidpp::feature::rgb_effects::{
CLUSTER_EFFECT_PARAM_COUNT, PowerModeTarget, RgbEffectsFeature, RgbPersistence, SwControlFlags,
};
use hidpp::protocol::v20::Hidpp20Error;
use openlogi_core::color::Rgb;
use tracing::warn;
use super::{
FRAME_GAP, HidppOperation, WriteError, WriteScope, classify_hidpp_error, open_feature,
};
const FEATURE: u16 = 0x8071;
const STATIC_RGB: u16 = 1;
#[cfg(test)]
mod tests;
pub(super) async fn apply(
channel: &Arc<HidppChannel>,
index: u8,
color: Rgb,
scope: &WriteScope<impl Fn() -> bool, impl Fn() -> bool>,
) -> Result<(), WriteError> {
let feature = scope
.read(async {
let mut device = Device::new(Arc::clone(channel), index)
.await
.map_err(|_| WriteError::DeviceUnreachable { index })?;
open_feature::<RgbEffectsFeature>(&mut device).await
})
.await?;
let effects = discover(&feature, scope).await?;
let previous = scope
.read(async { feature.get_sw_control().await.map_err(classify) })
.await?;
scope.check()?;
let result = async {
feature
.set_sw_control(
previous.control | SwControlFlags::ALL_CLUSTERS,
previous.events,
)
.await
.map_err(classify)?;
let (r, g, b) = color.components();
let mut params = [0; CLUSTER_EFFECT_PARAM_COUNT];
params[..3].copy_from_slice(&[r, g, b]);
for (cluster, effect) in effects {
scope.check()?;
feature
.set_rgb_cluster_effect(
cluster,
effect,
params,
RgbPersistence::VOLATILE,
PowerModeTarget::FullPower,
)
.await
.map_err(classify)?;
tokio::time::sleep(FRAME_GAP).await;
}
scope.check()
}
.await;
if let Err(error) = result {
let restored = async {
scope.check_available()?;
feature
.set_sw_control(previous.control, previous.events)
.await
.map_err(classify)
}
.await;
if let Err(restore_error) = restored {
warn!(
index,
?error,
?restore_error,
"RGB control restoration incomplete"
);
return Err(restore_error);
}
return Err(error);
}
Ok(())
}
async fn discover(
feature: &RgbEffectsFeature,
scope: &WriteScope<impl Fn() -> bool, impl Fn() -> bool>,
) -> Result<Vec<(u8, u8)>, WriteError> {
let info = scope
.read(async { feature.get_device_info().await.map_err(classify) })
.await?;
let mut effects = Vec::with_capacity(usize::from(info.cluster_count));
for cluster in 0..info.cluster_count {
let info = scope
.read(async { feature.get_cluster_info(cluster).await.map_err(classify) })
.await?;
let mut found = None;
for effect in 0..info.effects_number {
let info = scope
.read(async {
feature
.get_effect_info(cluster, effect)
.await
.map_err(classify)
})
.await?;
if info.effect_id == STATIC_RGB {
found = Some(effect);
break;
}
}
let effect = found.ok_or(WriteError::FeatureUnsupported {
feature_hex: FEATURE,
})?;
effects.push((cluster, effect));
}
if effects.is_empty() {
return Err(WriteError::FeatureUnsupported {
feature_hex: FEATURE,
});
}
Ok(effects)
}
fn classify(error: Hidpp20Error) -> WriteError {
match error {
Hidpp20Error::Channel(ChannelError::Timeout) => WriteError::RequestTimedOut {
operation: HidppOperation::Lighting,
},
other => classify_hidpp_error(other, HidppOperation::Lighting, FEATURE),
}
}