hidpp/feature/color_led_effects/
mod.rs1pub mod event;
13pub mod types;
14
15#[cfg(test)]
16mod tests;
17
18use std::sync::Arc;
19
20pub use event::ColorLedEffectsEvent;
21pub use types::{
22 ColorLedInfo, CyclingDirection, EffectId, EffectSettings, ExtCapabilities, LedBinIndex,
23 LedBinInfo, LocationEffect, NvCapabilities, NvCapabilityState, NvConfig, Persistence,
24 PersistenceSource, PersistencyCapabilities, Rgb, SwControl, SwControlState,
25 ZONE_EFFECT_PARAM_COUNT, ZoneEffect, ZoneEffectInfo, ZoneInfo,
26};
27
28use self::types::be16;
29use crate::{
30 channel::{HidppChannel, MessageListenerGuard},
31 event::EventEmitter,
32 feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
33 protocol::v20::{ErrorType, Hidpp20Error},
34};
35
36pub struct ColorLedEffectsFeature {
38 endpoint: FeatureEndpoint,
40
41 emitter: Arc<EventEmitter<ColorLedEffectsEvent>>,
43
44 _msg_listener: MessageListenerGuard,
46}
47
48impl CreatableFeature for ColorLedEffectsFeature {
49 const ID: u16 = 0x8070;
50 const STARTING_VERSION: u8 = 0;
51
52 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
53 let emitter = Arc::new(EventEmitter::new());
54
55 let listener = chan.add_msg_listener_guarded({
56 let emitter = Arc::clone(&emitter);
57
58 move |raw, matched| {
59 let Some((func, payload)) =
60 event_payload(raw, matched, device_index, feature_index)
61 else {
62 return;
63 };
64 if let Some(event) = event::decode_event(func.to_lo(), &payload) {
65 emitter.emit(event);
66 }
67 }
68 });
69
70 Self {
71 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
72 emitter,
73 _msg_listener: listener,
74 }
75 }
76}
77
78impl Feature for ColorLedEffectsFeature {}
79
80impl EmittingFeature<ColorLedEffectsEvent> for ColorLedEffectsFeature {
81 fn listen(&self) -> async_channel::Receiver<ColorLedEffectsEvent> {
82 self.emitter.create_receiver()
83 }
84}
85
86impl ColorLedEffectsFeature {
87 pub async fn get_info(&self) -> Result<ColorLedInfo, Hidpp20Error> {
89 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
90 Ok(ColorLedInfo::from_payload(&payload))
91 }
92
93 pub async fn get_zone_info(&self, zone_index: u8) -> Result<ZoneInfo, Hidpp20Error> {
95 let payload = self
96 .endpoint
97 .call(1, [zone_index, 0, 0])
98 .await?
99 .extend_payload();
100 ZoneInfo::from_payload(&payload)
101 }
102
103 pub async fn get_zone_effect_info(
105 &self,
106 zone_index: u8,
107 zone_effect_index: u8,
108 ) -> Result<ZoneEffectInfo, Hidpp20Error> {
109 let payload = self
110 .endpoint
111 .call(2, [zone_index, zone_effect_index, 0])
112 .await?
113 .extend_payload();
114 ZoneEffectInfo::from_payload(&payload)
115 }
116
117 pub async fn set_zone_effect(
125 &self,
126 zone_index: u8,
127 zone_effect_index: u8,
128 params: [u8; ZONE_EFFECT_PARAM_COUNT],
129 persistence: Persistence,
130 ) -> Result<(), Hidpp20Error> {
131 let mut args = [0; 16];
132 args[0] = zone_index;
133 args[1] = zone_effect_index;
134 args[2..2 + ZONE_EFFECT_PARAM_COUNT].copy_from_slice(¶ms);
135 args[12] = persistence.into();
136 self.endpoint.call_long(3, args).await?;
137 Ok(())
138 }
139
140 pub async fn get_nv_config(
144 &self,
145 capability: NvCapabilities,
146 ) -> Result<NvConfig, Hidpp20Error> {
147 validate_single_nv_capability(capability)?;
148 let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
149 let payload = self
150 .endpoint
151 .call(4, [cap_hi, cap_lo, 0])
152 .await?
153 .extend_payload();
154 Ok(NvConfig {
155 capability: NvCapabilities::from_bits_retain(be16(&payload, 0)),
156 state: NvCapabilityState::try_from(payload[2])
157 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
158 param1: payload[3],
159 param2: payload[4],
160 })
161 }
162
163 pub async fn set_nv_config(
165 &self,
166 capability: NvCapabilities,
167 state: NvCapabilityState,
168 param1: u8,
169 param2: u8,
170 ) -> Result<(), Hidpp20Error> {
171 validate_single_nv_capability(capability)?;
172 let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
173 let mut args = [0; 16];
174 args[..5].copy_from_slice(&[cap_hi, cap_lo, state.into(), param1, param2]);
175 self.endpoint.call_long(5, args).await?;
176 Ok(())
177 }
178
179 pub async fn get_led_bin_info(
181 &self,
182 zone_index: u8,
183 led_bin_index: LedBinIndex,
184 ) -> Result<LedBinInfo, Hidpp20Error> {
185 let payload = self
186 .endpoint
187 .call(6, [zone_index, led_bin_index.into(), 0])
188 .await?
189 .extend_payload();
190 LedBinInfo::from_payload(&payload)
191 }
192
193 pub async fn get_sw_control(&self) -> Result<SwControlState, Hidpp20Error> {
195 let payload = self.endpoint.call(7, [0; 3]).await?.extend_payload();
196 Ok(SwControlState {
197 control: SwControl::try_from(payload[0])
198 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
199 sync_events: payload[1] != 0,
200 })
201 }
202
203 pub async fn set_sw_control(
208 &self,
209 control: SwControl,
210 sync_events: bool,
211 ) -> Result<(), Hidpp20Error> {
212 self.endpoint
213 .call(8, [control.into(), u8::from(sync_events), 0])
214 .await?;
215 Ok(())
216 }
217
218 pub async fn get_effect_settings(
222 &self,
223 zone_index: u8,
224 source: PersistenceSource,
225 ) -> Result<EffectSettings, Hidpp20Error> {
226 let payload = self
227 .endpoint
228 .call(9, [zone_index, source.into(), 0])
229 .await?
230 .extend_payload();
231 Ok(EffectSettings::from_payload(&payload))
232 }
233
234 pub async fn clear_effect_settings(&self, zone_index: u8) -> Result<(), Hidpp20Error> {
237 self.endpoint.call(10, [zone_index, 0, 0]).await?;
238 Ok(())
239 }
240
241 pub async fn set_cycling_direction(
243 &self,
244 direction: CyclingDirection,
245 ) -> Result<(), Hidpp20Error> {
246 self.endpoint.call(11, [direction.into(), 0, 0]).await?;
247 Ok(())
248 }
249
250 pub async fn get_current_color(&self, zone_index: u8) -> Result<Rgb, Hidpp20Error> {
252 let payload = self
253 .endpoint
254 .call(12, [zone_index, 0, 0])
255 .await?
256 .extend_payload();
257 Ok(Rgb {
258 red: payload[1],
259 green: payload[2],
260 blue: payload[3],
261 })
262 }
263
264 pub async fn synchronize_effect(
270 &self,
271 zone_index: u8,
272 drift_value: i16,
273 ) -> Result<(), Hidpp20Error> {
274 let [drift_hi, drift_lo] = drift_value.to_be_bytes();
275 let mut args = [0; 16];
276 args[..4].copy_from_slice(&[zone_index, 0, drift_hi, drift_lo]);
277 self.endpoint.call_long(13, args).await?;
278 Ok(())
279 }
280
281 pub async fn get_zone_effect(
285 &self,
286 zone_index: u8,
287 source: PersistenceSource,
288 ) -> Result<ZoneEffect, Hidpp20Error> {
289 let payload = self
290 .endpoint
291 .call(14, [zone_index, source.into(), 0])
292 .await?
293 .extend_payload();
294 Ok(ZoneEffect::from_payload(&payload))
295 }
296
297 pub async fn set_led_bin_info(&self, info: &LedBinInfo) -> Result<LedBinInfo, Hidpp20Error> {
301 let mut args = [0; 16];
302 args[0] = info.zone_index;
303 args[1] = info.led_bin_index.into();
304 args[2..4].copy_from_slice(&info.red.to_be_bytes());
305 args[4..6].copy_from_slice(&info.green.to_be_bytes());
306 args[6..8].copy_from_slice(&info.blue.to_be_bytes());
307 args[8..10].copy_from_slice(&info.white.to_be_bytes());
308 let payload = self.endpoint.call_long(15, args).await?.extend_payload();
309 LedBinInfo::from_payload(&payload)
310 }
311}
312
313fn validate_single_nv_capability(capability: NvCapabilities) -> Result<(), Hidpp20Error> {
314 if capability.bits().count_ones() != 1 {
315 return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
316 }
317 Ok(())
318}