1pub mod event;
13pub mod types;
14
15#[cfg(test)]
16mod tests;
17
18use openlogi_hidpp_derive::Feature;
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 feature::{EventSource, FeatureEndpoint},
31 protocol::v20::{ErrorType, Hidpp20Error},
32};
33
34#[derive(Feature)]
36#[creatable(id = 0x8070, version = 0)]
37pub struct ColorLedEffectsFeature {
38 endpoint: FeatureEndpoint,
40
41 events: EventSource<ColorLedEffectsEvent>,
43}
44
45impl ColorLedEffectsFeature {
46 pub async fn get_info(&self) -> Result<ColorLedInfo, Hidpp20Error> {
48 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
49 Ok(ColorLedInfo::from_payload(&payload))
50 }
51
52 pub async fn get_zone_info(&self, zone_index: u8) -> Result<ZoneInfo, Hidpp20Error> {
54 let payload = self
55 .endpoint
56 .call(1, [zone_index, 0, 0])
57 .await?
58 .extend_payload();
59 ZoneInfo::from_payload(&payload)
60 }
61
62 pub async fn get_zone_effect_info(
64 &self,
65 zone_index: u8,
66 zone_effect_index: u8,
67 ) -> Result<ZoneEffectInfo, Hidpp20Error> {
68 let payload = self
69 .endpoint
70 .call(2, [zone_index, zone_effect_index, 0])
71 .await?
72 .extend_payload();
73 ZoneEffectInfo::from_payload(&payload)
74 }
75
76 pub async fn set_zone_effect(
84 &self,
85 zone_index: u8,
86 zone_effect_index: u8,
87 params: [u8; ZONE_EFFECT_PARAM_COUNT],
88 persistence: Persistence,
89 ) -> Result<(), Hidpp20Error> {
90 let mut args = [0; 16];
91 args[0] = zone_index;
92 args[1] = zone_effect_index;
93 args[2..2 + ZONE_EFFECT_PARAM_COUNT].copy_from_slice(¶ms);
94 args[12] = persistence.into();
95 self.endpoint.call_long(3, args).await?;
96 Ok(())
97 }
98
99 pub async fn get_nv_config(
103 &self,
104 capability: NvCapabilities,
105 ) -> Result<NvConfig, Hidpp20Error> {
106 validate_single_nv_capability(capability)?;
107 let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
108 let payload = self
109 .endpoint
110 .call(4, [cap_hi, cap_lo, 0])
111 .await?
112 .extend_payload();
113 Ok(NvConfig {
114 capability: NvCapabilities::from_bits_retain(be16(&payload, 0)),
115 state: NvCapabilityState::try_from(payload[2])
116 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
117 param1: payload[3],
118 param2: payload[4],
119 })
120 }
121
122 pub async fn set_nv_config(
124 &self,
125 capability: NvCapabilities,
126 state: NvCapabilityState,
127 param1: u8,
128 param2: u8,
129 ) -> Result<(), Hidpp20Error> {
130 validate_single_nv_capability(capability)?;
131 let [cap_hi, cap_lo] = capability.bits().to_be_bytes();
132 let mut args = [0; 16];
133 args[..5].copy_from_slice(&[cap_hi, cap_lo, state.into(), param1, param2]);
134 self.endpoint.call_long(5, args).await?;
135 Ok(())
136 }
137
138 pub async fn get_led_bin_info(
140 &self,
141 zone_index: u8,
142 led_bin_index: LedBinIndex,
143 ) -> Result<LedBinInfo, Hidpp20Error> {
144 let payload = self
145 .endpoint
146 .call(6, [zone_index, led_bin_index.into(), 0])
147 .await?
148 .extend_payload();
149 LedBinInfo::from_payload(&payload)
150 }
151
152 pub async fn get_sw_control(&self) -> Result<SwControlState, Hidpp20Error> {
154 let payload = self.endpoint.call(7, [0; 3]).await?.extend_payload();
155 Ok(SwControlState {
156 control: SwControl::try_from(payload[0])
157 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
158 sync_events: payload[1] != 0,
159 })
160 }
161
162 pub async fn set_sw_control(
167 &self,
168 control: SwControl,
169 sync_events: bool,
170 ) -> Result<(), Hidpp20Error> {
171 self.endpoint
172 .call(8, [control.into(), u8::from(sync_events), 0])
173 .await?;
174 Ok(())
175 }
176
177 pub async fn get_effect_settings(
181 &self,
182 zone_index: u8,
183 source: PersistenceSource,
184 ) -> Result<EffectSettings, Hidpp20Error> {
185 let payload = self
186 .endpoint
187 .call(9, [zone_index, source.into(), 0])
188 .await?
189 .extend_payload();
190 Ok(EffectSettings::from_payload(&payload))
191 }
192
193 pub async fn clear_effect_settings(&self, zone_index: u8) -> Result<(), Hidpp20Error> {
196 self.endpoint.call(10, [zone_index, 0, 0]).await?;
197 Ok(())
198 }
199
200 pub async fn set_cycling_direction(
202 &self,
203 direction: CyclingDirection,
204 ) -> Result<(), Hidpp20Error> {
205 self.endpoint.call(11, [direction.into(), 0, 0]).await?;
206 Ok(())
207 }
208
209 pub async fn get_current_color(&self, zone_index: u8) -> Result<Rgb, Hidpp20Error> {
211 let payload = self
212 .endpoint
213 .call(12, [zone_index, 0, 0])
214 .await?
215 .extend_payload();
216 Ok(Rgb {
217 red: payload[1],
218 green: payload[2],
219 blue: payload[3],
220 })
221 }
222
223 pub async fn synchronize_effect(
229 &self,
230 zone_index: u8,
231 drift_value: i16,
232 ) -> Result<(), Hidpp20Error> {
233 let [drift_hi, drift_lo] = drift_value.to_be_bytes();
234 let mut args = [0; 16];
235 args[..4].copy_from_slice(&[zone_index, 0, drift_hi, drift_lo]);
236 self.endpoint.call_long(13, args).await?;
237 Ok(())
238 }
239
240 pub async fn get_zone_effect(
244 &self,
245 zone_index: u8,
246 source: PersistenceSource,
247 ) -> Result<ZoneEffect, Hidpp20Error> {
248 let payload = self
249 .endpoint
250 .call(14, [zone_index, source.into(), 0])
251 .await?
252 .extend_payload();
253 Ok(ZoneEffect::from_payload(&payload))
254 }
255
256 pub async fn set_led_bin_info(&self, info: &LedBinInfo) -> Result<LedBinInfo, Hidpp20Error> {
260 let mut args = [0; 16];
261 args[0] = info.zone_index;
262 args[1] = info.led_bin_index.into();
263 args[2..4].copy_from_slice(&info.red.to_be_bytes());
264 args[4..6].copy_from_slice(&info.green.to_be_bytes());
265 args[6..8].copy_from_slice(&info.blue.to_be_bytes());
266 args[8..10].copy_from_slice(&info.white.to_be_bytes());
267 let payload = self.endpoint.call_long(15, args).await?.extend_payload();
268 LedBinInfo::from_payload(&payload)
269 }
270}
271
272fn validate_single_nv_capability(capability: NvCapabilities) -> Result<(), Hidpp20Error> {
273 if capability.bits().count_ones() != 1 {
274 return Err(Hidpp20Error::Feature(ErrorType::InvalidArgument));
275 }
276 Ok(())
277}