1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
27use std::cmp::PartialEq;
28use std::convert::{TryFrom, TryInto};
29use std::ffi::{CStr, CString};
30use std::io;
31use std::io::Cursor;
32use thiserror::Error;
33
34#[cfg(fuzzing)]
35#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
36#[derive(Debug, Clone)]
37pub struct ComparableFloat(f32);
38#[cfg(fuzzing)]
39impl PartialEq for ComparableFloat {
40 fn eq(&self, other: &Self) -> bool {
41 if self.0.is_nan() && other.0.is_nan() {
42 true
43 } else {
44 self.0 == other.0
45 }
46 }
47}
48#[cfg(fuzzing)]
49impl From<f32> for ComparableFloat {
50 fn from(f: f32) -> Self {
51 ComparableFloat(f)
52 }
53}
54
55#[derive(Error, Debug)]
57pub enum Error {
58 #[error("unknown message type: `{0}`")]
63 UnknownMessageType(u16),
64 #[error("protocol error: `{0}`")]
66 ProtocolError(String),
67
68 #[error("i/o error")]
69 Io(#[from] io::Error),
70}
71
72impl From<std::convert::Infallible> for Error {
73 fn from(_: std::convert::Infallible) -> Self {
74 unreachable!()
75 }
76}
77
78impl TryFrom<u8> for ApplicationRequest {
79 type Error = Error;
80 fn try_from(val: u8) -> Result<ApplicationRequest, Error> {
81 match val {
82 0 => Ok(ApplicationRequest::NoApply),
83 1 => Ok(ApplicationRequest::Apply),
84 2 => Ok(ApplicationRequest::ApplyOnly),
85 x => Err(Error::ProtocolError(format!(
86 "Unknown application request {}",
87 x
88 ))),
89 }
90 }
91}
92
93impl TryFrom<u8> for Waveform {
94 type Error = Error;
95 fn try_from(val: u8) -> Result<Waveform, Error> {
96 match val {
97 0 => Ok(Waveform::Saw),
98 1 => Ok(Waveform::Sine),
99 2 => Ok(Waveform::HalfSign),
100 3 => Ok(Waveform::Triangle),
101 4 => Ok(Waveform::Pulse),
102 x => Err(Error::ProtocolError(format!(
103 "Unknown waveform value {}",
104 x
105 ))),
106 }
107 }
108}
109
110impl TryFrom<u8> for Service {
111 type Error = Error;
112 fn try_from(val: u8) -> Result<Service, Error> {
113 match val {
114 x if x == Service::UDP as u8 => Ok(Service::UDP),
115 x if x == Service::Reserved1 as u8 => Ok(Service::Reserved1),
116 x if x == Service::Reserved2 as u8 => Ok(Service::Reserved2),
117 x if x == Service::Reserved3 as u8 => Ok(Service::Reserved3),
118 x if x == Service::Reserved4 as u8 => Ok(Service::Reserved4),
119 val => Err(Error::ProtocolError(format!(
120 "Unknown service value {}",
121 val
122 ))),
123 }
124 }
125}
126
127impl TryFrom<u16> for PowerLevel {
128 type Error = Error;
129 fn try_from(val: u16) -> Result<PowerLevel, Error> {
130 match val {
131 x if x == PowerLevel::Enabled as u16 => Ok(PowerLevel::Enabled),
132 x if x == PowerLevel::Standby as u16 => Ok(PowerLevel::Standby),
133 x => Err(Error::ProtocolError(format!("Unknown power level {}", x))),
134 }
135 }
136}
137
138#[derive(Copy, Clone, PartialEq, Eq)]
139#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
140pub struct EchoPayload(pub [u8; 64]);
141
142impl std::fmt::Debug for EchoPayload {
143 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
144 write!(f, "<EchoPayload>")
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
150pub struct LifxIdent(pub [u8; 16]);
151
152#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct LifxString(CString);
155
156impl LifxString {
157 pub fn new(s: &CStr) -> LifxString {
159 let mut b = s.to_bytes().to_vec();
160 if b.len() > 31 {
161 b[31] = 0;
162 let b = b[..32].to_vec();
163 LifxString(unsafe {
164 CString::from_vec_with_nul_unchecked(b)
166 })
167 } else {
168 LifxString(s.to_owned())
169 }
170 }
171 pub fn cstr(&self) -> &CStr {
172 &self.0
173 }
174}
175
176impl std::fmt::Display for LifxString {
177 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
178 write!(fmt, "{}", self.0.to_string_lossy())
179 }
180}
181
182impl std::cmp::PartialEq<str> for LifxString {
183 fn eq(&self, other: &str) -> bool {
184 self.0.to_string_lossy() == other
185 }
186}
187
188#[cfg(feature = "arbitrary")]
189impl<'a> arbitrary::Arbitrary<'a> for LifxString {
190 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
191 let len: usize = u.int_in_range(0..=31)?;
193
194 let mut v = Vec::new();
195 for _ in 0..len {
196 let b: std::num::NonZeroU8 = u.arbitrary()?;
197 v.push(b);
198 }
199
200 let s = CString::from(v);
201 assert!(s.to_bytes_with_nul().len() <= 32);
202 Ok(LifxString(s))
203 }
204}
205
206trait LittleEndianWriter<T>: WriteBytesExt {
207 fn write_val(&mut self, v: T) -> Result<(), io::Error>;
208}
209
210macro_rules! derive_writer {
211{ $( $m:ident: $t:ty ),*} => {
212 $(
213 impl<T: WriteBytesExt> LittleEndianWriter<$t> for T {
214 fn write_val(&mut self, v: $t) -> Result<(), io::Error> {
215 self . $m ::<LittleEndian>(v)
216 }
217 }
218 )*
219
220}
221}
222
223derive_writer! { write_u32: u32, write_u16: u16, write_i16: i16, write_u64: u64, write_f32: f32 }
224
225#[cfg(fuzzing)]
226impl<T: WriteBytesExt> LittleEndianWriter<ComparableFloat> for T {
227 fn write_val(&mut self, v: ComparableFloat) -> Result<(), io::Error> {
228 self.write_f32::<LittleEndian>(v.0)
229 }
230}
231
232impl<T: WriteBytesExt> LittleEndianWriter<u8> for T {
233 fn write_val(&mut self, v: u8) -> Result<(), io::Error> {
234 self.write_u8(v)
235 }
236}
237
238impl<T: WriteBytesExt> LittleEndianWriter<bool> for T {
239 fn write_val(&mut self, v: bool) -> Result<(), io::Error> {
240 self.write_u8(if v { 1 } else { 0 })
241 }
242}
243
244impl<T> LittleEndianWriter<LifxString> for T
245where
246 T: WriteBytesExt,
247{
248 fn write_val(&mut self, v: LifxString) -> Result<(), io::Error> {
249 let b = v.0.to_bytes();
250 for idx in 0..32 {
251 if idx >= b.len() {
252 self.write_u8(0)?;
253 } else {
254 self.write_u8(b[idx])?;
255 }
256 }
257 Ok(())
258 }
259}
260
261impl<T> LittleEndianWriter<LifxIdent> for T
262where
263 T: WriteBytesExt,
264{
265 fn write_val(&mut self, v: LifxIdent) -> Result<(), io::Error> {
266 for idx in 0..16 {
267 self.write_u8(v.0[idx])?;
268 }
269 Ok(())
270 }
271}
272
273impl<T> LittleEndianWriter<EchoPayload> for T
274where
275 T: WriteBytesExt,
276{
277 fn write_val(&mut self, v: EchoPayload) -> Result<(), io::Error> {
278 for idx in 0..64 {
279 self.write_u8(v.0[idx])?;
280 }
281 Ok(())
282 }
283}
284
285impl<T> LittleEndianWriter<HSBK> for T
286where
287 T: WriteBytesExt,
288{
289 fn write_val(&mut self, v: HSBK) -> Result<(), io::Error> {
290 self.write_val(v.hue)?;
291 self.write_val(v.saturation)?;
292 self.write_val(v.brightness)?;
293 self.write_val(v.kelvin)?;
294 Ok(())
295 }
296}
297
298impl<T> LittleEndianWriter<PowerLevel> for T
299where
300 T: WriteBytesExt,
301{
302 fn write_val(&mut self, v: PowerLevel) -> Result<(), io::Error> {
303 self.write_u16::<LittleEndian>(v as u16)
304 }
305}
306
307impl<T> LittleEndianWriter<ApplicationRequest> for T
308where
309 T: WriteBytesExt,
310{
311 fn write_val(&mut self, v: ApplicationRequest) -> Result<(), io::Error> {
312 self.write_u8(v as u8)
313 }
314}
315
316impl<T> LittleEndianWriter<Waveform> for T
317where
318 T: WriteBytesExt,
319{
320 fn write_val(&mut self, v: Waveform) -> Result<(), io::Error> {
321 self.write_u8(v as u8)
322 }
323}
324
325impl<T> LittleEndianWriter<LastHevCycleResult> for T
326where
327 T: WriteBytesExt,
328{
329 fn write_val(&mut self, v: LastHevCycleResult) -> Result<(), io::Error> {
330 self.write_u8(v as u8)
331 }
332}
333
334impl<T> LittleEndianWriter<MultiZoneEffectType> for T
335where
336 T: WriteBytesExt,
337{
338 fn write_val(&mut self, v: MultiZoneEffectType) -> Result<(), io::Error> {
339 self.write_u8(v as u8)
340 }
341}
342
343impl<T> LittleEndianWriter<&Box<[HSBK; 82]>> for T
344where
345 T: WriteBytesExt,
346{
347 fn write_val(&mut self, v: &Box<[HSBK; 82]>) -> Result<(), io::Error> {
348 for elem in &**v {
349 self.write_val(*elem)?;
350 }
351 Ok(())
352 }
353}
354
355impl<T> LittleEndianWriter<&[u8; 32]> for T
356where
357 T: WriteBytesExt,
358{
359 fn write_val(&mut self, v: &[u8; 32]) -> Result<(), io::Error> {
360 self.write_all(v)
361 }
362}
363
364impl<T> LittleEndianWriter<&[u32; 8]> for T
365where
366 T: WriteBytesExt,
367{
368 fn write_val(&mut self, v: &[u32; 8]) -> Result<(), io::Error> {
369 for elem in v {
370 self.write_u32::<LittleEndian>(*elem)?;
371 }
372 Ok(())
373 }
374}
375
376trait LittleEndianReader<T> {
377 fn read_val(&mut self) -> Result<T, io::Error>;
378}
379
380macro_rules! derive_reader {
381{ $( $m:ident: $t:ty ),*} => {
382 $(
383 impl<T: ReadBytesExt> LittleEndianReader<$t> for T {
384 fn read_val(&mut self) -> Result<$t, io::Error> {
385 self . $m ::<LittleEndian>()
386 }
387 }
388 )*
389
390}
391}
392
393derive_reader! { read_u32: u32, read_u16: u16, read_i16: i16, read_u64: u64, read_f32: f32 }
394
395impl<R: ReadBytesExt> LittleEndianReader<u8> for R {
396 fn read_val(&mut self) -> Result<u8, io::Error> {
397 self.read_u8()
398 }
399}
400
401impl<R: ReadBytesExt> LittleEndianReader<bool> for R {
402 fn read_val(&mut self) -> Result<bool, io::Error> {
403 Ok(self.read_u8()? > 0)
404 }
405}
406
407impl<R: ReadBytesExt> LittleEndianReader<LastHevCycleResult> for R {
408 fn read_val(&mut self) -> Result<LastHevCycleResult, io::Error> {
409 let val: u8 = self.read_val()?;
410 match val {
411 0 => Ok(LastHevCycleResult::Success),
412 1 => Ok(LastHevCycleResult::Busy),
413 2 => Ok(LastHevCycleResult::InterruptedByReset),
414 3 => Ok(LastHevCycleResult::InterruptedByHomekit),
415 4 => Ok(LastHevCycleResult::InterruptedByLan),
416 5 => Ok(LastHevCycleResult::InterruptedByCloud),
417 _ => Ok(LastHevCycleResult::None),
418 }
419 }
420}
421
422impl<R: ReadBytesExt> LittleEndianReader<MultiZoneEffectType> for R {
423 fn read_val(&mut self) -> Result<MultiZoneEffectType, io::Error> {
424 let val: u8 = self.read_val()?;
425 match val {
426 0 => Ok(MultiZoneEffectType::Off),
427 1 => Ok(MultiZoneEffectType::Move),
428 2 => Ok(MultiZoneEffectType::Reserved1),
429 _ => Ok(MultiZoneEffectType::Reserved2),
430 }
431 }
432}
433
434impl<R: ReadBytesExt> LittleEndianReader<[u8; 32]> for R {
435 fn read_val(&mut self) -> Result<[u8; 32], io::Error> {
436 let mut data = [0; 32];
437 self.read_exact(&mut data)?;
438 Ok(data)
439 }
440}
441
442impl<R: ReadBytesExt> LittleEndianReader<[u32; 8]> for R {
443 fn read_val(&mut self) -> Result<[u32; 8], io::Error> {
444 let mut data = [0; 8];
445 for x in &mut data {
446 *x = self.read_u32::<LittleEndian>()?;
447 }
448 Ok(data)
449 }
450}
451
452impl<R: ReadBytesExt> LittleEndianReader<[HSBK; 82]> for R {
453 fn read_val(&mut self) -> Result<[HSBK; 82], io::Error> {
454 let mut data = [HSBK {
455 hue: 0,
456 saturation: 0,
457 brightness: 0,
458 kelvin: 0,
459 }; 82];
460 for x in &mut data {
461 *x = self.read_val()?;
462 }
463
464 Ok(data)
465 }
466}
467
468impl<R: ReadBytesExt> LittleEndianReader<HSBK> for R {
469 fn read_val(&mut self) -> Result<HSBK, io::Error> {
470 let hue = self.read_val()?;
471 let sat = self.read_val()?;
472 let bri = self.read_val()?;
473 let kel = self.read_val()?;
474 Ok(HSBK {
475 hue,
476 saturation: sat,
477 brightness: bri,
478 kelvin: kel,
479 })
480 }
481}
482
483impl<R: ReadBytesExt> LittleEndianReader<LifxIdent> for R {
484 fn read_val(&mut self) -> Result<LifxIdent, io::Error> {
485 let mut val = [0; 16];
486 for v in &mut val {
487 *v = self.read_val()?;
488 }
489 Ok(LifxIdent(val))
490 }
491}
492
493impl<R: ReadBytesExt> LittleEndianReader<LifxString> for R {
494 fn read_val(&mut self) -> Result<LifxString, io::Error> {
495 let mut bytes = Vec::new();
496 for _ in 0..31 {
497 let c: u8 = self.read_val()?;
498 if let Some(b) = std::num::NonZeroU8::new(c) {
499 bytes.push(b);
500 }
501 }
502 self.read_u8()?;
504
505 Ok(LifxString(CString::from(bytes)))
506 }
507}
508
509impl<R: ReadBytesExt> LittleEndianReader<EchoPayload> for R {
510 fn read_val(&mut self) -> Result<EchoPayload, io::Error> {
511 let mut val = [0; 64];
512 for v in val.iter_mut() {
513 *v = self.read_val()?;
514 }
515 Ok(EchoPayload(val))
516 }
517}
518
519impl<R: ReadBytesExt> LittleEndianReader<PowerLevel> for R {
520 fn read_val(&mut self) -> Result<PowerLevel, io::Error> {
521 let val: u16 = self.read_val()?;
522 if val == 0 {
523 Ok(PowerLevel::Standby)
524 } else {
525 Ok(PowerLevel::Enabled)
526 }
527 }
528}
529
530impl<R: ReadBytesExt> LittleEndianReader<Waveform> for R {
531 fn read_val(&mut self) -> Result<Waveform, io::Error> {
532 let v = self.read_u8()?;
533 match v {
534 0 => Ok(Waveform::Saw),
535 1 => Ok(Waveform::Sine),
536 2 => Ok(Waveform::HalfSign),
537 3 => Ok(Waveform::Triangle),
538 4 => Ok(Waveform::Pulse),
539 _ => Ok(Waveform::Saw), }
541 }
542}
543
544macro_rules! unpack {
545 ($msg:ident, $typ:ident, $( $n:ident: $t:ty ),*) => {
546 {
547 let mut c = Cursor::new(&$msg.payload);
548 $(
549 let $n: $t = c.read_val()?;
550 )*
551
552 Message::$typ {
553 $(
554 $n: $n.try_into()?,
555 )*
556 }
557 }
558 };
559}
560
561#[repr(u8)]
584#[derive(Debug, Copy, Clone, PartialEq, Eq)]
585#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
586pub enum Service {
587 UDP = 1,
588 Reserved1 = 2,
589 Reserved2 = 3,
590 Reserved3 = 4,
591 Reserved4 = 5,
592}
593
594#[repr(u16)]
595#[derive(Debug, Copy, Clone, PartialEq, Eq)]
596#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
597pub enum PowerLevel {
598 Standby = 0,
599 Enabled = 65535,
600}
601
602#[repr(u8)]
606#[derive(Debug, Copy, Clone, PartialEq, Eq)]
607#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
608pub enum ApplicationRequest {
609 NoApply = 0,
611 Apply = 1,
613 ApplyOnly = 2,
615}
616
617#[repr(u8)]
618#[derive(Debug, Copy, Clone, PartialEq, Eq)]
619#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
620pub enum Waveform {
621 Saw = 0,
622 Sine = 1,
623 HalfSign = 2,
624 Triangle = 3,
625 Pulse = 4,
626}
627
628#[repr(u8)]
629#[derive(Debug, Copy, Clone, PartialEq, Eq)]
630#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
631pub enum LastHevCycleResult {
632 Success = 0,
633 Busy = 1,
634 InterruptedByReset = 2,
635 InterruptedByHomekit = 3,
636 InterruptedByLan = 4,
637 InterruptedByCloud = 5,
638 None = 255,
639}
640
641#[repr(u8)]
642#[derive(Debug, Copy, Clone, PartialEq, Eq)]
643#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
644pub enum MultiZoneEffectType {
645 Off = 0,
646 Move = 1,
647 Reserved1 = 2,
648 Reserved2 = 3,
649}
650
651#[derive(Clone, Debug, PartialEq)]
658#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
659pub enum Message {
660 GetService,
665
666 StateService {
673 service: Service,
675 port: u32,
678 },
679
680 GetHostInfo,
685
686 StateHostInfo {
692 #[cfg(not(fuzzing))]
694 signal: f32,
695 #[cfg(fuzzing)]
696 signal: ComparableFloat,
697 tx: u32,
699 rx: u32,
701 reserved: i16,
702 },
703
704 GetHostFirmware,
710
711 StateHostFirmware {
717 build: u64,
719 reserved: u64,
720 version_minor: u16,
722 version_major: u16,
724 },
725
726 GetWifiInfo,
731
732 StateWifiInfo {
740 #[cfg(not(fuzzing))]
745 signal: f32,
746 #[cfg(fuzzing)]
747 signal: ComparableFloat,
748 reserved6: u32,
752 reserved7: u32,
756 reserved: i16,
757 },
758
759 GetWifiFirmware,
765
766 StateWifiFirmware {
772 build: u64,
774 reserved: u64,
775 version_minor: u16,
777 version_major: u16,
779 },
780
781 GetPower,
787
788 SetPower {
792 level: PowerLevel,
796 },
797
798 StatePower {
804 level: u16,
810 },
811
812 GetLabel,
819
820 SetLabel { label: LifxString },
824
825 StateLabel { label: LifxString },
831
832 GetVersion,
838
839 StateVersion {
846 vendor: u32,
850 product: u32,
852 reserved: u32,
856 },
857
858 GetInfo,
864
865 StateInfo {
871 time: u64,
877 uptime: u64,
879 downtime: u64,
881 },
882
883 Acknowledgement { seq: u8 },
890
891 GetLocation,
897
898 SetLocation {
902 location: LifxIdent,
904 label: LifxString,
906 updated_at: u64,
908 },
909
910 StateLocation {
914 location: LifxIdent,
915 label: LifxString,
916 updated_at: u64,
917 },
918
919 GetGroup,
925
926 SetGroup {
930 group: LifxIdent,
931 label: LifxString,
932 updated_at: u64,
933 },
934
935 StateGroup {
939 group: LifxIdent,
941 label: LifxString,
943 updated_at: u64,
945 },
946
947 EchoRequest { payload: EchoPayload },
953
954 EchoResponse { payload: EchoPayload },
960
961 LightGet,
967
968 LightSetColor {
975 reserved: u8,
976 color: HSBK,
978 duration: u32,
980 },
981
982 SetWaveform {
986 reserved: u8,
987 transient: bool,
988 color: HSBK,
989 period: u32,
991 #[cfg(not(fuzzing))]
993 cycles: f32,
994 #[cfg(fuzzing)]
995 cycles: ComparableFloat,
996 skew_ratio: i16,
998 waveform: Waveform,
1000 },
1001
1002 LightState {
1008 color: HSBK,
1009 reserved: i16,
1010 power: u16,
1012 label: LifxString,
1014 reserved2: u64,
1015 },
1016
1017 LightGetPower,
1023
1024 LightSetPower { level: u16, duration: u32 },
1033
1034 LightStatePower { level: u16 },
1038
1039 SetWaveformOptional {
1043 reserved: u8,
1044 transient: bool,
1045 color: HSBK,
1046 period: u32,
1048 #[cfg(not(fuzzing))]
1050 cycles: f32,
1051 #[cfg(fuzzing)]
1052 cycles: ComparableFloat,
1053
1054 skew_ratio: i16,
1055 waveform: Waveform,
1056 set_hue: bool,
1057 set_saturation: bool,
1058 set_brightness: bool,
1059 set_kelvin: bool,
1060 },
1061
1062 LightGetInfrared,
1066
1067 LightStateInfrared { brightness: u16 },
1071
1072 LightSetInfrared { brightness: u16 },
1076
1077 LightGetHevCycle,
1085
1086 LightSetHevCycle {
1088 enable: bool,
1090 duration: u32,
1094 },
1095
1096 LightStateHevCycle {
1100 duration: u32,
1102 remaining: u32,
1104 last_power: bool,
1108 },
1109
1110 LightGetHevCycleConfiguration,
1116
1117 LightSetHevCycleConfiguration { indication: bool, duration: u32 },
1119
1120 LightStateHevCycleConfiguration { indication: bool, duration: u32 },
1122
1123 LightGetLastHevCycleResult,
1125
1126 LightStateLastHevCycleResult { result: LastHevCycleResult },
1128
1129 SetColorZones {
1135 start_index: u8,
1136 end_index: u8,
1137 color: HSBK,
1138 duration: u32,
1139 apply: ApplicationRequest,
1140 },
1141
1142 GetColorZones { start_index: u8, end_index: u8 },
1152
1153 StateZone { count: u8, index: u8, color: HSBK },
1159
1160 StateMultiZone {
1168 count: u8,
1169 index: u8,
1170 color0: HSBK,
1171 color1: HSBK,
1172 color2: HSBK,
1173 color3: HSBK,
1174 color4: HSBK,
1175 color5: HSBK,
1176 color6: HSBK,
1177 color7: HSBK,
1178 },
1179
1180 GetMultiZoneEffect,
1182
1183 SetMultiZoneEffect {
1185 instance_id: u32,
1187 typ: MultiZoneEffectType,
1188 reserved: u16,
1189 speed: u32,
1191 duration: u64,
1193 reserved7: u32,
1194 reserved8: u32,
1195 parameters: [u32; 8],
1197 },
1198
1199 StateMultiZoneEffect {
1201 instance_id: u32,
1203 typ: MultiZoneEffectType,
1204 reserved: u16,
1205 speed: u32,
1207 duration: u64,
1209 reserved7: u32,
1210 reserved8: u32,
1211 parameters: [u32; 8],
1213 },
1214
1215 SetExtendedColorZones {
1217 duration: u32,
1218 apply: ApplicationRequest,
1219 zone_index: u16,
1220 colors_count: u8,
1221 colors: Box<[HSBK; 82]>,
1222 },
1223
1224 GetExtendedColorZone,
1226
1227 StateExtendedColorZones {
1229 zones_count: u16,
1230 zone_index: u16,
1231 colors_count: u8,
1232 colors: Box<[HSBK; 82]>,
1233 },
1234
1235 RelayGetPower {
1241 relay_index: u8,
1243 },
1244
1245 RelaySetPower {
1247 relay_index: u8,
1249 level: u16,
1254 },
1255
1256 RelayStatePower {
1260 relay_index: u8,
1262 level: u16,
1267 },
1268}
1269
1270impl Message {
1271 pub fn get_num(&self) -> u16 {
1275 match *self {
1276 Message::GetService => 2,
1277 Message::StateService { .. } => 3,
1278 Message::GetHostInfo => 12,
1279 Message::StateHostInfo { .. } => 13,
1280 Message::GetHostFirmware => 14,
1281 Message::StateHostFirmware { .. } => 15,
1282 Message::GetWifiInfo => 16,
1283 Message::StateWifiInfo { .. } => 17,
1284 Message::GetWifiFirmware => 18,
1285 Message::StateWifiFirmware { .. } => 19,
1286 Message::GetPower => 20,
1287 Message::SetPower { .. } => 21,
1288 Message::StatePower { .. } => 22,
1289 Message::GetLabel => 23,
1290 Message::SetLabel { .. } => 24,
1291 Message::StateLabel { .. } => 25,
1292 Message::GetVersion => 32,
1293 Message::StateVersion { .. } => 33,
1294 Message::GetInfo => 34,
1295 Message::StateInfo { .. } => 35,
1296 Message::Acknowledgement { .. } => 45,
1297 Message::GetLocation => 48,
1298 Message::SetLocation { .. } => 49,
1299 Message::StateLocation { .. } => 50,
1300 Message::GetGroup => 51,
1301 Message::SetGroup { .. } => 52,
1302 Message::StateGroup { .. } => 53,
1303 Message::EchoRequest { .. } => 58,
1304 Message::EchoResponse { .. } => 59,
1305 Message::LightGet => 101,
1306 Message::LightSetColor { .. } => 102,
1307 Message::SetWaveform { .. } => 103,
1308 Message::LightState { .. } => 107,
1309 Message::LightGetPower => 116,
1310 Message::LightSetPower { .. } => 117,
1311 Message::LightStatePower { .. } => 118,
1312 Message::SetWaveformOptional { .. } => 119,
1313 Message::LightGetInfrared => 120,
1314 Message::LightStateInfrared { .. } => 121,
1315 Message::LightSetInfrared { .. } => 122,
1316 Message::LightGetHevCycle => 142,
1317 Message::LightSetHevCycle { .. } => 143,
1318 Message::LightStateHevCycle { .. } => 144,
1319 Message::LightGetHevCycleConfiguration => 145,
1320 Message::LightSetHevCycleConfiguration { .. } => 146,
1321 Message::LightStateHevCycleConfiguration { .. } => 147,
1322 Message::LightGetLastHevCycleResult => 148,
1323 Message::LightStateLastHevCycleResult { .. } => 149,
1324 Message::SetColorZones { .. } => 501,
1325 Message::GetColorZones { .. } => 502,
1326 Message::StateZone { .. } => 503,
1327 Message::StateMultiZone { .. } => 506,
1328 Message::GetMultiZoneEffect => 507,
1329 Message::SetMultiZoneEffect { .. } => 508,
1330 Message::StateMultiZoneEffect { .. } => 509,
1331 Message::SetExtendedColorZones { .. } => 510,
1332 Message::GetExtendedColorZone => 511,
1333 Message::StateExtendedColorZones { .. } => 512,
1334 Message::RelayGetPower { .. } => 816,
1335 Message::RelaySetPower { .. } => 817,
1336 Message::RelayStatePower { .. } => 818,
1337 }
1338 }
1339
1340 pub fn from_raw(msg: &RawMessage) -> Result<Message, Error> {
1342 match msg.protocol_header.typ {
1343 2 => Ok(Message::GetService),
1344 3 => Ok(unpack!(msg, StateService, service: u8, port: u32)),
1345 12 => Ok(Message::GetHostInfo),
1346 13 => Ok(unpack!(
1347 msg,
1348 StateHostInfo,
1349 signal: f32,
1350 tx: u32,
1351 rx: u32,
1352 reserved: i16
1353 )),
1354 14 => Ok(Message::GetHostFirmware),
1355 15 => Ok(unpack!(
1356 msg,
1357 StateHostFirmware,
1358 build: u64,
1359 reserved: u64,
1360 version_minor: u16,
1361 version_major: u16
1362 )),
1363 16 => Ok(Message::GetWifiInfo),
1364 17 => Ok(unpack!(
1365 msg,
1366 StateWifiInfo,
1367 signal: f32,
1368 reserved6: u32,
1369 reserved7: u32,
1370 reserved: i16
1371 )),
1372 18 => Ok(Message::GetWifiFirmware),
1373 19 => Ok(unpack!(
1374 msg,
1375 StateWifiFirmware,
1376 build: u64,
1377 reserved: u64,
1378 version_minor: u16,
1379 version_major: u16
1380 )),
1381 20 => Ok(Message::GetPower),
1382 21 => Ok(unpack!(msg, SetPower, level: PowerLevel)),
1383 22 => Ok(unpack!(msg, StatePower, level: u16)),
1384 23 => Ok(Message::GetLabel),
1385 24 => Ok(unpack!(msg, SetLabel, label: LifxString)),
1386 25 => Ok(unpack!(msg, StateLabel, label: LifxString)),
1387 32 => Ok(Message::GetVersion),
1388 33 => Ok(unpack!(
1389 msg,
1390 StateVersion,
1391 vendor: u32,
1392 product: u32,
1393 reserved: u32
1394 )),
1395 34 => Ok(Message::GetInfo),
1396 35 => Ok(unpack!(
1397 msg,
1398 StateInfo,
1399 time: u64,
1400 uptime: u64,
1401 downtime: u64
1402 )),
1403 45 => Ok(Message::Acknowledgement {
1404 seq: msg.frame_addr.sequence,
1405 }),
1406 48 => Ok(Message::GetLocation),
1407 49 => Ok(unpack!(
1408 msg,
1409 SetLocation,
1410 location: LifxIdent,
1411 label: LifxString,
1412 updated_at: u64
1413 )),
1414 50 => Ok(unpack!(
1415 msg,
1416 StateLocation,
1417 location: LifxIdent,
1418 label: LifxString,
1419 updated_at: u64
1420 )),
1421 51 => Ok(Message::GetGroup),
1422 52 => Ok(unpack!(
1423 msg,
1424 SetGroup,
1425 group: LifxIdent,
1426 label: LifxString,
1427 updated_at: u64
1428 )),
1429 53 => Ok(unpack!(
1430 msg,
1431 StateGroup,
1432 group: LifxIdent,
1433 label: LifxString,
1434 updated_at: u64
1435 )),
1436 58 => Ok(unpack!(msg, EchoRequest, payload: EchoPayload)),
1437 59 => Ok(unpack!(msg, EchoResponse, payload: EchoPayload)),
1438 101 => Ok(Message::LightGet),
1439 102 => Ok(unpack!(
1440 msg,
1441 LightSetColor,
1442 reserved: u8,
1443 color: HSBK,
1444 duration: u32
1445 )),
1446 103 => Ok(unpack!(
1447 msg,
1448 SetWaveform,
1449 reserved: u8,
1450 transient: bool,
1451 color: HSBK,
1452 period: u32,
1453 cycles: f32,
1454 skew_ratio: i16,
1455 waveform: Waveform
1456 )),
1457 107 => Ok(unpack!(
1458 msg,
1459 LightState,
1460 color: HSBK,
1461 reserved: i16,
1462 power: u16,
1463 label: LifxString,
1464 reserved2: u64
1465 )),
1466 116 => Ok(Message::LightGetPower),
1467 117 => Ok(unpack!(msg, LightSetPower, level: u16, duration: u32)),
1468 118 => {
1469 let mut c = Cursor::new(&msg.payload);
1470 Ok(Message::LightStatePower {
1471 level: c.read_val()?,
1472 })
1473 }
1474 119 => Ok(unpack!(
1475 msg,
1476 SetWaveformOptional,
1477 reserved: u8,
1478 transient: bool,
1479 color: HSBK,
1480 period: u32,
1481 cycles: f32,
1482 skew_ratio: i16,
1483 waveform: Waveform,
1484 set_hue: bool,
1485 set_saturation: bool,
1486 set_brightness: bool,
1487 set_kelvin: bool
1488 )),
1489 120 => Ok(Message::LightGetInfrared),
1490 122 => Ok(unpack!(msg, LightSetInfrared, brightness: u16)),
1491 142 => Ok(Message::LightGetHevCycle),
1492 143 => Ok(unpack!(msg, LightSetHevCycle, enable: bool, duration: u32)),
1493 144 => Ok(unpack!(
1494 msg,
1495 LightStateHevCycle,
1496 duration: u32,
1497 remaining: u32,
1498 last_power: bool
1499 )),
1500 145 => Ok(Message::LightGetHevCycleConfiguration),
1501 146 => Ok(unpack!(
1502 msg,
1503 LightSetHevCycleConfiguration,
1504 indication: bool,
1505 duration: u32
1506 )),
1507 147 => Ok(unpack!(
1508 msg,
1509 LightStateHevCycleConfiguration,
1510 indication: bool,
1511 duration: u32
1512 )),
1513 148 => Ok(Message::LightGetLastHevCycleResult),
1514 149 => Ok(unpack!(
1515 msg,
1516 LightStateLastHevCycleResult,
1517 result: LastHevCycleResult
1518 )),
1519 121 => Ok(unpack!(msg, LightStateInfrared, brightness: u16)),
1520 501 => Ok(unpack!(
1521 msg,
1522 SetColorZones,
1523 start_index: u8,
1524 end_index: u8,
1525 color: HSBK,
1526 duration: u32,
1527 apply: u8
1528 )),
1529 502 => Ok(unpack!(msg, GetColorZones, start_index: u8, end_index: u8)),
1530 503 => Ok(unpack!(msg, StateZone, count: u8, index: u8, color: HSBK)),
1531 506 => Ok(unpack!(
1532 msg,
1533 StateMultiZone,
1534 count: u8,
1535 index: u8,
1536 color0: HSBK,
1537 color1: HSBK,
1538 color2: HSBK,
1539 color3: HSBK,
1540 color4: HSBK,
1541 color5: HSBK,
1542 color6: HSBK,
1543 color7: HSBK
1544 )),
1545 507 => Ok(Message::GetMultiZoneEffect),
1546 508 => Ok(unpack!(
1547 msg,
1548 SetMultiZoneEffect,
1549 instance_id: u32,
1550 typ: MultiZoneEffectType,
1551 reserved: u16,
1552 speed: u32,
1553 duration: u64,
1554 reserved7: u32,
1555 reserved8: u32,
1556 parameters: [u32; 8]
1557 )),
1558 509 => Ok(unpack!(
1559 msg,
1560 StateMultiZoneEffect,
1561 instance_id: u32,
1562 typ: MultiZoneEffectType,
1563 reserved: u16,
1564 speed: u32,
1565 duration: u64,
1566 reserved7: u32,
1567 reserved8: u32,
1568 parameters: [u32; 8]
1569 )),
1570 510 => Ok(unpack!(
1571 msg,
1572 SetExtendedColorZones,
1573 duration: u32,
1574 apply: u8,
1575 zone_index: u16,
1576 colors_count: u8,
1577 colors: [HSBK; 82]
1578 )),
1579 511 => Ok(Message::GetExtendedColorZone),
1580 512 => Ok(unpack!(
1581 msg,
1582 StateExtendedColorZones,
1583 zones_count: u16,
1584 zone_index: u16,
1585 colors_count: u8,
1586 colors: [HSBK; 82]
1587 )),
1588 816 => Ok(unpack!(msg, RelayGetPower, relay_index: u8)),
1589 817 => Ok(unpack!(msg, RelaySetPower, relay_index: u8, level: u16)),
1590 818 => Ok(unpack!(msg, RelayStatePower, relay_index: u8, level: u16)),
1591 _ => Err(Error::UnknownMessageType(msg.protocol_header.typ)),
1592 }
1593 }
1594}
1595
1596#[derive(Debug, Copy, Clone, PartialEq, Eq)]
1611#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
1612pub struct HSBK {
1613 pub hue: u16,
1614 pub saturation: u16,
1615 pub brightness: u16,
1616 pub kelvin: u16,
1617}
1618
1619impl HSBK {
1620 pub fn describe(&self, short: bool) -> String {
1621 match short {
1622 true if self.saturation == 0 => format!("{}K", self.kelvin),
1623 true => format!(
1624 "{:.0}/{:.0}",
1625 (self.hue as f32 / 65535.0) * 360.0,
1626 self.saturation as f32 / 655.35
1627 ),
1628 false if self.saturation == 0 => format!(
1629 "{:.0}% White ({})",
1630 self.brightness as f32 / 655.35,
1631 describe_kelvin(self.kelvin)
1632 ),
1633 false => format!(
1634 "{}% hue: {} sat: {}",
1635 self.brightness as f32 / 655.35,
1636 self.hue,
1637 self.saturation
1638 ),
1639 }
1640 }
1641}
1642
1643pub fn describe_kelvin(k: u16) -> &'static str {
1647 if k <= 2500 {
1648 "Ultra Warm"
1649 } else if k > 2500 && k <= 2700 {
1650 "Incandescent"
1651 } else if k > 2700 && k <= 3000 {
1652 "Warm"
1653 } else if k > 300 && k <= 3200 {
1654 "Neutral Warm"
1655 } else if k > 3200 && k <= 3500 {
1656 "Neutral"
1657 } else if k > 3500 && k <= 4000 {
1658 "Cool"
1659 } else if k > 400 && k <= 4500 {
1660 "Cool Daylight"
1661 } else if k > 4500 && k <= 5000 {
1662 "Soft Daylight"
1663 } else if k > 5000 && k <= 5500 {
1664 "Daylight"
1665 } else if k > 5500 && k <= 6000 {
1666 "Noon Daylight"
1667 } else if k > 6000 && k <= 6500 {
1668 "Bright Daylight"
1669 } else if k > 6500 && k <= 7000 {
1670 "Cloudy Daylight"
1671 } else if k > 7000 && k <= 7500 {
1672 "Blue Daylight"
1673 } else if k > 7500 && k <= 8000 {
1674 "Blue Overcast"
1675 } else if k > 8000 && k <= 8500 {
1676 "Blue Water"
1677 } else {
1678 "Blue Ice"
1679 }
1680}
1681
1682impl HSBK {}
1683
1684#[derive(Debug, Clone, PartialEq, Eq)]
1690pub struct RawMessage {
1691 pub frame: Frame,
1692 pub frame_addr: FrameAddress,
1693 pub protocol_header: ProtocolHeader,
1694 pub payload: Vec<u8>,
1695}
1696
1697#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1708pub struct Frame {
1709 pub size: u16,
1711
1712 pub origin: u8,
1714
1715 pub tagged: bool,
1717
1718 pub addressable: bool,
1720
1721 pub protocol: u16,
1723
1724 pub source: u32,
1732}
1733
1734#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1741pub struct FrameAddress {
1742 pub target: u64,
1744
1745 pub reserved: [u8; 6],
1747
1748 pub reserved2: u8,
1750
1751 pub ack_required: bool,
1753
1754 pub res_required: bool,
1756
1757 pub sequence: u8,
1759}
1760
1761#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1762pub struct ProtocolHeader {
1763 pub reserved: u64,
1765
1766 pub typ: u16,
1770
1771 pub reserved2: u16,
1773}
1774
1775impl Frame {
1776 fn packed_size() -> usize {
1778 8
1779 }
1780
1781 fn validate(&self) {
1782 assert!(self.origin < 4);
1783 assert!(self.addressable);
1784 assert_eq!(self.protocol, 1024);
1785 }
1786
1787 fn pack(&self) -> Result<Vec<u8>, Error> {
1788 let mut v = Vec::with_capacity(Self::packed_size());
1789
1790 v.write_u16::<LittleEndian>(self.size)?;
1791
1792 let mut d: u16 = (<u16 as From<u8>>::from(self.origin) & 0b11) << 14;
1794 d += if self.tagged { 1 } else { 0 } << 13;
1795 d += if self.addressable { 1 } else { 0 } << 12;
1796 d += (self.protocol & 0b1111_1111_1111) as u16;
1797
1798 v.write_u16::<LittleEndian>(d)?;
1799
1800 v.write_u32::<LittleEndian>(self.source)?;
1801
1802 Ok(v)
1803 }
1804
1805 fn unpack(v: &[u8]) -> Result<Frame, Error> {
1806 let mut c = Cursor::new(v);
1807
1808 let size = c.read_val()?;
1809
1810 let d: u16 = c.read_val()?;
1812
1813 let origin: u8 = ((d & 0b1100_0000_0000_0000) >> 14) as u8;
1814 let tagged: bool = (d & 0b0010_0000_0000_0000) > 0;
1815 let addressable = (d & 0b0001_0000_0000_0000) > 0;
1816 let protocol: u16 = d & 0b0000_1111_1111_1111;
1817
1818 if protocol != 1024 {
1819 return Err(Error::ProtocolError(format!(
1820 "Unpacked frame had protocol version {}",
1821 protocol
1822 )));
1823 }
1824
1825 let source = c.read_val()?;
1826
1827 let frame = Frame {
1828 size,
1829 origin,
1830 tagged,
1831 addressable,
1832 protocol,
1833 source,
1834 };
1835 Ok(frame)
1836 }
1837}
1838
1839impl FrameAddress {
1840 fn packed_size() -> usize {
1841 16
1842 }
1843 fn validate(&self) {
1844 }
1847 fn pack(&self) -> Result<Vec<u8>, Error> {
1848 let mut v = Vec::with_capacity(Self::packed_size());
1849 v.write_u64::<LittleEndian>(self.target)?;
1850 for idx in 0..6 {
1851 v.write_u8(self.reserved[idx])?;
1852 }
1853
1854 let b: u8 = (self.reserved2 << 2)
1855 + if self.ack_required { 2 } else { 0 }
1856 + if self.res_required { 1 } else { 0 };
1857 v.write_u8(b)?;
1858 v.write_u8(self.sequence)?;
1859 Ok(v)
1860 }
1861
1862 fn unpack(v: &[u8]) -> Result<FrameAddress, Error> {
1863 let mut c = Cursor::new(v);
1864
1865 let target = c.read_val()?;
1866
1867 let mut reserved: [u8; 6] = [0; 6];
1868 for slot in &mut reserved {
1869 *slot = c.read_val()?;
1870 }
1871
1872 let b: u8 = c.read_val()?;
1873 let reserved2: u8 = (b & 0b1111_1100) >> 2;
1874 let ack_required = (b & 0b10) > 0;
1875 let res_required = (b & 0b01) > 0;
1876
1877 let sequence = c.read_val()?;
1878
1879 let f = FrameAddress {
1880 target,
1881 reserved,
1882 reserved2,
1883 ack_required,
1884 res_required,
1885 sequence,
1886 };
1887 f.validate();
1888 Ok(f)
1889 }
1890}
1891
1892impl ProtocolHeader {
1893 fn packed_size() -> usize {
1894 12
1895 }
1896 fn validate(&self) {
1897 }
1900
1901 pub fn pack(&self) -> Result<Vec<u8>, Error> {
1903 let mut v = Vec::with_capacity(Self::packed_size());
1904 v.write_u64::<LittleEndian>(self.reserved)?;
1905 v.write_u16::<LittleEndian>(self.typ)?;
1906 v.write_u16::<LittleEndian>(self.reserved2)?;
1907 Ok(v)
1908 }
1909 fn unpack(v: &[u8]) -> Result<ProtocolHeader, Error> {
1910 let mut c = Cursor::new(v);
1911
1912 let reserved = c.read_val()?;
1913 let typ = c.read_val()?;
1914 let reserved2 = c.read_val()?;
1915
1916 let f = ProtocolHeader {
1917 reserved,
1918 typ,
1919 reserved2,
1920 };
1921 f.validate();
1922 Ok(f)
1923 }
1924}
1925
1926#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1930pub struct BuildOptions {
1931 pub target: Option<u64>,
1936 pub ack_required: bool,
1940 pub res_required: bool,
1945 pub sequence: u8,
1951 pub source: u32,
1957}
1958
1959impl RawMessage {
1960 pub fn build(options: &BuildOptions, typ: Message) -> Result<RawMessage, Error> {
1966 let frame = Frame {
1967 size: 0,
1968 origin: 0,
1969 tagged: options.target.is_none(),
1970 addressable: true,
1971 protocol: 1024,
1972 source: options.source,
1973 };
1974 let addr = FrameAddress {
1975 target: options.target.unwrap_or(0),
1976 reserved: [0; 6],
1977 reserved2: 0,
1978 ack_required: options.ack_required,
1979 res_required: options.res_required,
1980 sequence: options.sequence,
1981 };
1982 let phead = ProtocolHeader {
1983 reserved: 0,
1984 reserved2: 0,
1985 typ: typ.get_num(),
1986 };
1987
1988 let mut v = Vec::new();
1989 match typ {
1990 Message::GetService
1991 | Message::GetHostInfo
1992 | Message::GetHostFirmware
1993 | Message::GetWifiFirmware
1994 | Message::GetWifiInfo
1995 | Message::GetPower
1996 | Message::GetLabel
1997 | Message::GetVersion
1998 | Message::GetInfo
1999 | Message::Acknowledgement { .. }
2000 | Message::GetLocation
2001 | Message::GetGroup
2002 | Message::LightGet
2003 | Message::LightGetPower
2004 | Message::LightGetInfrared
2005 | Message::LightGetHevCycle
2006 | Message::LightGetHevCycleConfiguration
2007 | Message::LightGetLastHevCycleResult
2008 | Message::GetMultiZoneEffect
2009 | Message::GetExtendedColorZone => {
2010 }
2012 Message::SetColorZones {
2013 start_index,
2014 end_index,
2015 color,
2016 duration,
2017 apply,
2018 } => {
2019 v.write_val(start_index)?;
2020 v.write_val(end_index)?;
2021 v.write_val(color)?;
2022 v.write_val(duration)?;
2023 v.write_val(apply)?;
2024 }
2025 Message::SetWaveform {
2026 reserved,
2027 transient,
2028 color,
2029 period,
2030 cycles,
2031 skew_ratio,
2032 waveform,
2033 } => {
2034 v.write_val(reserved)?;
2035 v.write_val(transient)?;
2036 v.write_val(color)?;
2037 v.write_val(period)?;
2038 v.write_val(cycles)?;
2039 v.write_val(skew_ratio)?;
2040 v.write_val(waveform)?;
2041 }
2042 Message::SetWaveformOptional {
2043 reserved,
2044 transient,
2045 color,
2046 period,
2047 cycles,
2048 skew_ratio,
2049 waveform,
2050 set_hue,
2051 set_saturation,
2052 set_brightness,
2053 set_kelvin,
2054 } => {
2055 v.write_val(reserved)?;
2056 v.write_val(transient)?;
2057 v.write_val(color)?;
2058 v.write_val(period)?;
2059 v.write_val(cycles)?;
2060 v.write_val(skew_ratio)?;
2061 v.write_val(waveform)?;
2062 v.write_val(set_hue)?;
2063 v.write_val(set_saturation)?;
2064 v.write_val(set_brightness)?;
2065 v.write_val(set_kelvin)?;
2066 }
2067 Message::GetColorZones {
2068 start_index,
2069 end_index,
2070 } => {
2071 v.write_val(start_index)?;
2072 v.write_val(end_index)?;
2073 }
2074 Message::StateZone {
2075 count,
2076 index,
2077 color,
2078 } => {
2079 v.write_val(count)?;
2080 v.write_val(index)?;
2081 v.write_val(color)?;
2082 }
2083 Message::StateMultiZone {
2084 count,
2085 index,
2086 color0,
2087 color1,
2088 color2,
2089 color3,
2090 color4,
2091 color5,
2092 color6,
2093 color7,
2094 } => {
2095 v.write_val(count)?;
2096 v.write_val(index)?;
2097 v.write_val(color0)?;
2098 v.write_val(color1)?;
2099 v.write_val(color2)?;
2100 v.write_val(color3)?;
2101 v.write_val(color4)?;
2102 v.write_val(color5)?;
2103 v.write_val(color6)?;
2104 v.write_val(color7)?;
2105 }
2106 Message::LightStateInfrared { brightness } => v.write_val(brightness)?,
2107 Message::LightSetInfrared { brightness } => v.write_val(brightness)?,
2108 Message::SetLocation {
2109 location,
2110 label,
2111 updated_at,
2112 } => {
2113 v.write_val(location)?;
2114 v.write_val(label)?;
2115 v.write_val(updated_at)?;
2116 }
2117 Message::SetGroup {
2118 group,
2119 label,
2120 updated_at,
2121 } => {
2122 v.write_val(group)?;
2123 v.write_val(label)?;
2124 v.write_val(updated_at)?;
2125 }
2126 Message::StateService { port, service } => {
2127 v.write_val(service as u8)?;
2128 v.write_val(port)?;
2129 }
2130 Message::StateHostInfo {
2131 signal,
2132 tx,
2133 rx,
2134 reserved,
2135 } => {
2136 v.write_val(signal)?;
2137 v.write_val(tx)?;
2138 v.write_val(rx)?;
2139 v.write_val(reserved)?;
2140 }
2141 Message::StateHostFirmware {
2142 build,
2143 reserved,
2144 version_minor,
2145 version_major,
2146 } => {
2147 v.write_val(build)?;
2148 v.write_val(reserved)?;
2149 v.write_val(version_minor)?;
2150 v.write_val(version_major)?;
2151 }
2152 Message::StateWifiInfo {
2153 signal,
2154 reserved6,
2155 reserved7,
2156 reserved,
2157 } => {
2158 v.write_val(signal)?;
2159 v.write_val(reserved6)?;
2160 v.write_val(reserved7)?;
2161 v.write_val(reserved)?;
2162 }
2163 Message::StateWifiFirmware {
2164 build,
2165 reserved,
2166 version_minor,
2167 version_major,
2168 } => {
2169 v.write_val(build)?;
2170 v.write_val(reserved)?;
2171 v.write_val(version_minor)?;
2172 v.write_val(version_major)?;
2173 }
2174 Message::SetPower { level } => {
2175 v.write_val(level)?;
2176 }
2177 Message::StatePower { level } => {
2178 v.write_val(level)?;
2179 }
2180 Message::SetLabel { label } => {
2181 v.write_val(label)?;
2182 }
2183 Message::StateLabel { label } => {
2184 v.write_val(label)?;
2185 }
2186 Message::StateVersion {
2187 vendor,
2188 product,
2189 reserved,
2190 } => {
2191 v.write_val(vendor)?;
2192 v.write_val(product)?;
2193 v.write_val(reserved)?;
2194 }
2195 Message::StateInfo {
2196 time,
2197 uptime,
2198 downtime,
2199 } => {
2200 v.write_val(time)?;
2201 v.write_val(uptime)?;
2202 v.write_val(downtime)?;
2203 }
2204 Message::StateLocation {
2205 location,
2206 label,
2207 updated_at,
2208 } => {
2209 v.write_val(location)?;
2210 v.write_val(label)?;
2211 v.write_val(updated_at)?;
2212 }
2213 Message::StateGroup {
2214 group,
2215 label,
2216 updated_at,
2217 } => {
2218 v.write_val(group)?;
2219 v.write_val(label)?;
2220 v.write_val(updated_at)?;
2221 }
2222 Message::EchoRequest { payload } => {
2223 v.write_val(payload)?;
2224 }
2225 Message::EchoResponse { payload } => {
2226 v.write_val(payload)?;
2227 }
2228 Message::LightSetColor {
2229 reserved,
2230 color,
2231 duration,
2232 } => {
2233 v.write_val(reserved)?;
2234 v.write_val(color)?;
2235 v.write_val(duration)?;
2236 }
2237 Message::LightState {
2238 color,
2239 reserved,
2240 power,
2241 label,
2242 reserved2,
2243 } => {
2244 v.write_val(color)?;
2245 v.write_val(reserved)?;
2246 v.write_val(power)?;
2247 v.write_val(label)?;
2248 v.write_val(reserved2)?;
2249 }
2250 Message::LightSetPower { level, duration } => {
2251 v.write_val(if level > 0 { 65535u16 } else { 0u16 })?;
2252 v.write_val(duration)?;
2253 }
2254 Message::LightStatePower { level } => {
2255 v.write_val(level)?;
2256 }
2257 Message::LightStateHevCycle {
2258 duration,
2259 remaining,
2260 last_power,
2261 } => {
2262 v.write_val(duration)?;
2263 v.write_val(remaining)?;
2264 v.write_val(last_power)?;
2265 }
2266 Message::LightStateHevCycleConfiguration {
2267 indication,
2268 duration,
2269 } => {
2270 v.write_val(indication)?;
2271 v.write_val(duration)?;
2272 }
2273 Message::LightStateLastHevCycleResult { result } => {
2274 v.write_val(result)?;
2275 }
2276 Message::SetMultiZoneEffect {
2277 instance_id,
2278 typ,
2279 reserved,
2280 speed,
2281 duration,
2282 reserved7,
2283 reserved8,
2284 parameters,
2285 } => {
2286 v.write_val(instance_id)?;
2287 v.write_val(typ)?;
2288 v.write_val(reserved)?;
2289 v.write_val(speed)?;
2290 v.write_val(duration)?;
2291 v.write_val(reserved7)?;
2292 v.write_val(reserved8)?;
2293 v.write_val(¶meters)?;
2294 }
2295 Message::StateMultiZoneEffect {
2296 instance_id,
2297 typ,
2298 reserved,
2299 speed,
2300 duration,
2301 reserved7,
2302 reserved8,
2303 parameters,
2304 } => {
2305 v.write_val(instance_id)?;
2306 v.write_val(typ)?;
2307 v.write_val(reserved)?;
2308 v.write_val(speed)?;
2309 v.write_val(duration)?;
2310 v.write_val(reserved7)?;
2311 v.write_val(reserved8)?;
2312 v.write_val(¶meters)?;
2313 }
2314 Message::SetExtendedColorZones {
2315 duration,
2316 apply,
2317 zone_index,
2318 colors_count,
2319 colors,
2320 } => {
2321 v.write_val(duration)?;
2322 v.write_val(apply)?;
2323 v.write_val(zone_index)?;
2324 v.write_val(colors_count)?;
2325 v.write_val(&colors)?;
2326 }
2327 Message::StateExtendedColorZones {
2328 zones_count,
2329 zone_index,
2330 colors_count,
2331 colors,
2332 } => {
2333 v.write_val(zones_count)?;
2334 v.write_val(zone_index)?;
2335 v.write_val(colors_count)?;
2336 v.write_val(&colors)?;
2337 }
2338 Message::RelayGetPower { relay_index } => {
2339 v.write_val(relay_index)?;
2340 }
2341 Message::RelayStatePower { relay_index, level } => {
2342 v.write_val(relay_index)?;
2343 v.write_val(level)?;
2344 }
2345 Message::RelaySetPower { relay_index, level } => {
2346 v.write_val(relay_index)?;
2347 v.write_val(level)?;
2348 }
2349 Message::LightSetHevCycle { enable, duration } => {
2350 v.write_val(enable)?;
2351 v.write_val(duration)?;
2352 }
2353 Message::LightSetHevCycleConfiguration {
2354 indication,
2355 duration,
2356 } => {
2357 v.write_val(indication)?;
2358 v.write_val(duration)?;
2359 }
2360 }
2361
2362 let mut msg = RawMessage {
2363 frame,
2364 frame_addr: addr,
2365 protocol_header: phead,
2366 payload: v,
2367 };
2368
2369 msg.frame.size = msg.packed_size() as u16;
2370
2371 Ok(msg)
2372 }
2373
2374 pub fn packed_size(&self) -> usize {
2376 Frame::packed_size()
2377 + FrameAddress::packed_size()
2378 + ProtocolHeader::packed_size()
2379 + self.payload.len()
2380 }
2381
2382 pub fn validate(&self) {
2384 self.frame.validate();
2385 self.frame_addr.validate();
2386 self.protocol_header.validate();
2387 }
2388
2389 pub fn pack(&self) -> Result<Vec<u8>, Error> {
2393 let mut v = Vec::with_capacity(self.packed_size());
2394 v.extend(self.frame.pack()?);
2395 v.extend(self.frame_addr.pack()?);
2396 v.extend(self.protocol_header.pack()?);
2397 v.extend(&self.payload);
2398 Ok(v)
2399 }
2400 pub fn unpack(v: &[u8]) -> Result<RawMessage, Error> {
2403 let mut start = 0;
2404 let frame = Frame::unpack(v)?;
2405 frame.validate();
2406 start += Frame::packed_size();
2407 let addr = FrameAddress::unpack(&v[start..])?;
2408 addr.validate();
2409 start += FrameAddress::packed_size();
2410 let proto = ProtocolHeader::unpack(&v[start..])?;
2411 proto.validate();
2412 start += ProtocolHeader::packed_size();
2413
2414 let body = Vec::from(&v[start..(frame.size as usize)]);
2415
2416 Ok(RawMessage {
2417 frame,
2418 frame_addr: addr,
2419 protocol_header: proto,
2420 payload: body,
2421 })
2422 }
2423}
2424
2425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2426pub enum TemperatureRange {
2427 Variable { min: u16, max: u16 },
2429 Fixed(u16),
2431 None,
2433}
2434
2435#[derive(Clone, Debug, Copy, PartialEq, Eq)]
2436pub struct ProductInfo {
2437 pub name: &'static str,
2438
2439 pub color: bool,
2441
2442 pub infrared: bool,
2444
2445 pub multizone: bool,
2447
2448 pub chain: bool,
2450
2451 pub hev: bool,
2453
2454 pub matrix: bool,
2456
2457 pub relays: bool,
2459
2460 pub buttons: bool,
2462
2463 pub temperature_range: TemperatureRange,
2465}
2466
2467#[rustfmt::skip]
2473pub fn get_product_info(vendor: u32, product: u32) -> Option<&'static ProductInfo> {
2474 match (vendor, product) {
2475 (1, 1) => Some(&ProductInfo { name: "LIFX Original 1000", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2476 (1, 3) => Some(&ProductInfo { name: "LIFX Color 650", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2477 (1, 10) => Some(&ProductInfo { name: "LIFX White 800 (Low Voltage)", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 6500 } }),
2478 (1, 11) => Some(&ProductInfo { name: "LIFX White 800 (High Voltage)", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 6500 } }),
2479 (1, 15) => Some(&ProductInfo { name: "LIFX Color 1000", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2480 (1, 18) => Some(&ProductInfo { name: "LIFX White 900 BR30 (Low Voltage)", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2481 (1, 19) => Some(&ProductInfo { name: "LIFX White 900 BR30 (High Voltage)", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2482 (1, 20) => Some(&ProductInfo { name: "LIFX Color 1000 BR30", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2483 (1, 22) => Some(&ProductInfo { name: "LIFX Color 1000", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2484 (1, 27) => Some(&ProductInfo { name: "LIFX A19", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2485 (1, 28) => Some(&ProductInfo { name: "LIFX BR30", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2486 (1, 29) => Some(&ProductInfo { name: "LIFX A19 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2487 (1, 30) => Some(&ProductInfo { name: "LIFX BR30 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2488 (1, 31) => Some(&ProductInfo { name: "LIFX Z", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2489 (1, 32) => Some(&ProductInfo { name: "LIFX Z", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2490 (1, 36) => Some(&ProductInfo { name: "LIFX Downlight", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2491 (1, 37) => Some(&ProductInfo { name: "LIFX Downlight", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2492 (1, 38) => Some(&ProductInfo { name: "LIFX Beam", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2493 (1, 39) => Some(&ProductInfo { name: "LIFX Downlight White to Warm", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2494 (1, 40) => Some(&ProductInfo { name: "LIFX Downlight", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2495 (1, 43) => Some(&ProductInfo { name: "LIFX A19", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2496 (1, 44) => Some(&ProductInfo { name: "LIFX BR30", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2497 (1, 45) => Some(&ProductInfo { name: "LIFX A19 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2498 (1, 46) => Some(&ProductInfo { name: "LIFX BR30 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2499 (1, 49) => Some(&ProductInfo { name: "LIFX Mini Color", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2500 (1, 50) => Some(&ProductInfo { name: "LIFX Mini White to Warm", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 6500 } }),
2501 (1, 51) => Some(&ProductInfo { name: "LIFX Mini White", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2502 (1, 52) => Some(&ProductInfo { name: "LIFX GU10", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2503 (1, 53) => Some(&ProductInfo { name: "LIFX GU10", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2504 (1, 55) => Some(&ProductInfo { name: "LIFX Tile", color: true, infrared: false, multizone: false, chain: true, hev: false, matrix: true, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2500, max: 9000 } }),
2505 (1, 57) => Some(&ProductInfo { name: "LIFX Candle", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: true, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2506 (1, 59) => Some(&ProductInfo { name: "LIFX Mini Color", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2507 (1, 60) => Some(&ProductInfo { name: "LIFX Mini White to Warm", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 6500 } }),
2508 (1, 61) => Some(&ProductInfo { name: "LIFX Mini White", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2509 (1, 62) => Some(&ProductInfo { name: "LIFX A19", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2510 (1, 63) => Some(&ProductInfo { name: "LIFX BR30", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2511 (1, 64) => Some(&ProductInfo { name: "LIFX A19 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2512 (1, 65) => Some(&ProductInfo { name: "LIFX BR30 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2513 (1, 66) => Some(&ProductInfo { name: "LIFX Mini White", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2514 (1, 68) => Some(&ProductInfo { name: "LIFX Candle", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: true, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2515 (1, 70) => Some(&ProductInfo { name: "LIFX Switch", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: true, buttons: true, temperature_range: TemperatureRange::None }),
2516 (1, 71) => Some(&ProductInfo { name: "LIFX Switch", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: true, buttons: true, temperature_range: TemperatureRange::None }),
2517 (1, 81) => Some(&ProductInfo { name: "LIFX Candle White to Warm", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2200, max: 6500 } }),
2518 (1, 82) => Some(&ProductInfo { name: "LIFX Filament Clear", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2100, max: 2100 } }),
2519 (1, 85) => Some(&ProductInfo { name: "LIFX Filament Amber", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2000, max: 2000 } }),
2520 (1, 87) => Some(&ProductInfo { name: "LIFX Mini White", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2521 (1, 88) => Some(&ProductInfo { name: "LIFX Mini White", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2522 (1, 89) => Some(&ProductInfo { name: "LIFX Switch", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: true, buttons: true, temperature_range: TemperatureRange::None }),
2523 (1, 90) => Some(&ProductInfo { name: "LIFX Clean", color: true, infrared: false, multizone: false, chain: false, hev: true, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2524 (1, 91) => Some(&ProductInfo { name: "LIFX Color", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2525 (1, 92) => Some(&ProductInfo { name: "LIFX Color", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2526 (1, 93) => Some(&ProductInfo { name: "LIFX A19 US", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2527 (1, 94) => Some(&ProductInfo { name: "LIFX BR30", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2528 (1, 96) => Some(&ProductInfo { name: "LIFX Candle White to Warm", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2200, max: 6500 } }),
2529 (1, 97) => Some(&ProductInfo { name: "LIFX A19", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2530 (1, 98) => Some(&ProductInfo { name: "LIFX BR30", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2531 (1, 99) => Some(&ProductInfo { name: "LIFX Clean", color: true, infrared: false, multizone: false, chain: false, hev: true, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2532 (1, 100) => Some(&ProductInfo { name: "LIFX Filament Clear", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2100, max: 2100 } }),
2533 (1, 101) => Some(&ProductInfo { name: "LIFX Filament Amber", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2000, max: 2000 } }),
2534 (1, 109) => Some(&ProductInfo { name: "LIFX A19 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2535 (1, 110) => Some(&ProductInfo { name: "LIFX BR30 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2536 (1, 111) => Some(&ProductInfo { name: "LIFX A19 Night Vision", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2537 (1, 112) => Some(&ProductInfo { name: "LIFX BR30 Night Vision Intl", color: true, infrared: true, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2538 (1, 113) => Some(&ProductInfo { name: "LIFX Mini WW US", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2539 (1, 114) => Some(&ProductInfo { name: "LIFX Mini WW Intl", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2540 (1, 115) => Some(&ProductInfo { name: "LIFX Switch", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: true, buttons: true, temperature_range: TemperatureRange::None }),
2541 (1, 116) => Some(&ProductInfo { name: "LIFX Switch", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: true, buttons: true, temperature_range: TemperatureRange::None }),
2542 (1, 117) => Some(&ProductInfo { name: "LIFX Z US", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2543 (1, 118) => Some(&ProductInfo { name: "LIFX Z Intl", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2544 (1, 119) => Some(&ProductInfo { name: "LIFX Beam US", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2545 (1, 120) => Some(&ProductInfo { name: "LIFX Beam Intl", color: true, infrared: false, multizone: true, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2546 (1, 123) => Some(&ProductInfo { name: "LIFX Color US", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2547 (1, 124) => Some(&ProductInfo { name: "LIFX Color Intl", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2548 (1, 125) => Some(&ProductInfo { name: "LIFX White to Warm US", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2549 (1, 126) => Some(&ProductInfo { name: "LIFX White to Warm Intl", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2550 (1, 127) => Some(&ProductInfo { name: "LIFX White US", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2551 (1, 128) => Some(&ProductInfo { name: "LIFX White Intl", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2552 (1, 129) => Some(&ProductInfo { name: "LIFX Color US", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2553 (1, 130) => Some(&ProductInfo { name: "LIFX Color Intl", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2554 (1, 131) => Some(&ProductInfo { name: "LIFX White To Warm US", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2555 (1, 132) => Some(&ProductInfo { name: "LIFX White To Warm Intl", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2556 (1, 133) => Some(&ProductInfo { name: "LIFX White US", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2557 (1, 134) => Some(&ProductInfo { name: "LIFX White Intl", color: false, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 2700, max: 2700 } }),
2558 (1, 135) => Some(&ProductInfo { name: "LIFX GU10 Color US", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2559 (1, 136) => Some(&ProductInfo { name: "LIFX GU10 Color Intl", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: false, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2560 (1, 137) => Some(&ProductInfo { name: "LIFX Candle Color US", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: true, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2561 (1, 138) => Some(&ProductInfo { name: "LIFX Candle Color Intl", color: true, infrared: false, multizone: false, chain: false, hev: false, matrix: true, relays: false, buttons: false, temperature_range: TemperatureRange::Variable { min: 1500, max: 9000 } }),
2562 (_, _) => None
2563 }
2564}
2565
2566#[cfg(test)]
2567mod tests {
2568 use super::*;
2569
2570 #[test]
2571 fn test_frame() {
2572 let frame = Frame {
2573 size: 0x1122,
2574 origin: 0,
2575 tagged: true,
2576 addressable: true,
2577 protocol: 1024,
2578 source: 1234567,
2579 };
2580 frame.validate();
2581
2582 let v = frame.pack().unwrap();
2583 println!("{:?}", v);
2584 assert_eq!(v[0], 0x22);
2585 assert_eq!(v[1], 0x11);
2586
2587 assert_eq!(v.len(), Frame::packed_size());
2588
2589 let unpacked = Frame::unpack(&v).unwrap();
2590 assert_eq!(frame, unpacked);
2591 }
2592
2593 #[test]
2594 fn test_decode_frame() {
2595 let v = vec![0x28, 0x00, 0x00, 0x54, 0x42, 0x52, 0x4b, 0x52];
2597 let frame = Frame::unpack(&v).unwrap();
2598 println!("{:?}", frame);
2599
2600 assert_eq!(frame.size, 0x0028);
2612 assert_eq!(frame.origin, 1);
2613 assert!(frame.addressable);
2614 assert!(!frame.tagged);
2615 assert_eq!(frame.protocol, 1024);
2616 assert_eq!(frame.source, 0x524b5242);
2617 }
2618
2619 #[test]
2620 fn test_decode_frame1() {
2621 let v = vec![0x24, 0x00, 0x00, 0x14, 0xca, 0x41, 0x37, 0x05];
2623 let frame = Frame::unpack(&v).unwrap();
2624 println!("{:?}", frame);
2625
2626 assert_eq!(frame.size, 0x0024);
2629 assert_eq!(frame.origin, 0);
2630 assert!(!frame.tagged);
2631 assert!(frame.addressable);
2632 assert_eq!(frame.protocol, 1024);
2633 assert_eq!(frame.source, 0x053741ca);
2634 }
2635
2636 #[test]
2637 fn test_frame_address() {
2638 let frame = FrameAddress {
2639 target: 0x11224488,
2640 reserved: [0; 6],
2641 reserved2: 0,
2642 ack_required: true,
2643 res_required: false,
2644 sequence: 248,
2645 };
2646 frame.validate();
2647
2648 let v = frame.pack().unwrap();
2649 assert_eq!(v.len(), FrameAddress::packed_size());
2650 println!("Packed FrameAddress: {:?}", v);
2651
2652 let unpacked = FrameAddress::unpack(&v).unwrap();
2653 assert_eq!(frame, unpacked);
2654 }
2655
2656 #[test]
2657 fn test_decode_frame_address() {
2658 let v = vec![
2660 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2661 0x01, 0x9c,
2662 ];
2663 assert_eq!(v.len(), FrameAddress::packed_size());
2664
2665 let frame = FrameAddress::unpack(&v).unwrap();
2666 frame.validate();
2667 println!("FrameAddress: {:?}", frame);
2668 }
2669
2670 #[test]
2671 fn test_protocol_header() {
2672 let frame = ProtocolHeader {
2673 reserved: 0,
2674 reserved2: 0,
2675 typ: 0x4455,
2676 };
2677 frame.validate();
2678
2679 let v = frame.pack().unwrap();
2680 assert_eq!(v.len(), ProtocolHeader::packed_size());
2681 println!("Packed ProtocolHeader: {:?}", v);
2682
2683 let unpacked = ProtocolHeader::unpack(&v).unwrap();
2684 assert_eq!(frame, unpacked);
2685 }
2686
2687 #[test]
2688 fn test_decode_protocol_header() {
2689 let v = vec![
2691 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
2692 ];
2693 assert_eq!(v.len(), ProtocolHeader::packed_size());
2694
2695 let frame = ProtocolHeader::unpack(&v).unwrap();
2696 frame.validate();
2697 println!("ProtocolHeader: {:?}", frame);
2698 }
2699
2700 #[test]
2701 fn test_decode_full() {
2702 let v = vec![
2703 0x24, 0x00, 0x00, 0x14, 0xca, 0x41, 0x37, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2704 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x00,
2705 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
2706 ];
2707
2708 let msg = RawMessage::unpack(&v).unwrap();
2709 msg.validate();
2710 println!("{:#?}", msg);
2711 }
2712
2713 #[test]
2714 fn test_decode_full_1() {
2715 let v = vec![
2716 0x58, 0x00, 0x00, 0x54, 0xca, 0x41, 0x37, 0x05, 0xd0, 0x73, 0xd5, 0x02, 0x97, 0xde,
2717 0x00, 0x00, 0x4c, 0x49, 0x46, 0x58, 0x56, 0x32, 0x00, 0xc0, 0x44, 0x30, 0xeb, 0x47,
2718 0xc4, 0x48, 0x18, 0x14, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
2719 0xb8, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x69, 0x74, 0x63, 0x68, 0x65, 0x6e, 0x00,
2720 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2721 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2722 0x00, 0x00, 0x00, 0x00,
2723 ];
2724
2725 let msg = RawMessage::unpack(&v).unwrap();
2726 msg.validate();
2727 println!("{:#?}", msg);
2728 }
2729
2730 #[test]
2731 fn test_build_a_packet() {
2732 let msg = Message::LightSetColor {
2735 reserved: 0,
2736 color: HSBK {
2737 hue: 21845,
2738 saturation: 0xffff,
2739 brightness: 0xffff,
2740 kelvin: 3500,
2741 },
2742 duration: 1024,
2743 };
2744
2745 let raw = RawMessage::build(
2746 &BuildOptions {
2747 target: None,
2748 ack_required: false,
2749 res_required: false,
2750 sequence: 0,
2751 source: 0,
2752 },
2753 msg,
2754 )
2755 .unwrap();
2756
2757 let bytes = raw.pack().unwrap();
2758 println!("{:?}", bytes);
2759 assert_eq!(bytes.len(), 49);
2760 assert_eq!(
2761 bytes,
2762 vec![
2763 0x31, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2764 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2765 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0xFF, 0xFF, 0xFF,
2766 0xFF, 0xAC, 0x0D, 0x00, 0x04, 0x00, 0x00
2767 ]
2768 );
2769 }
2770
2771 #[test]
2772 fn test_lifx_string() {
2773 let s = CStr::from_bytes_with_nul(b"hello\0").unwrap();
2774 let ls = LifxString::new(s);
2775 assert_eq!(ls.cstr(), s);
2776 assert!(ls.cstr().to_bytes_with_nul().len() <= 32);
2777
2778 let s = CStr::from_bytes_with_nul(b"this is bigger than thirty two characters\0").unwrap();
2779 let ls = LifxString::new(s);
2780 assert_eq!(ls.cstr().to_bytes_with_nul().len(), 32);
2781 assert_eq!(
2782 ls.cstr(),
2783 CStr::from_bytes_with_nul(b"this is bigger than thirty two \0").unwrap()
2784 );
2785 }
2786
2787 #[test]
2788 fn test_lifx_decode_setextendedlightzones_msg() {
2789 let v = vec![
2790 0xbc, 0x02, 0x00, 0x14, 0x10, 0x00, 0x3e, 0x8f, 0xd0, 0x73, 0xd5, 0x6f, 0x20, 0xad,
2791 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x47, 0x00, 0x00, 0x00, 0x00,
2792 0x00, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0x14, 0x05, 0x00, 0x00, 0x01, 0x00,
2793 0x00, 0x10, 0x54, 0xf5, 0x8e, 0xc2, 0x95, 0x7b, 0xac, 0x0d, 0x0a, 0xf6, 0x3c, 0xca,
2794 0x7e, 0x78, 0xac, 0x0d, 0xc0, 0xf6, 0xea, 0xd1, 0x67, 0x75, 0xac, 0x0d, 0x76, 0xf7,
2795 0x98, 0xd9, 0x50, 0x72, 0xac, 0x0d, 0x2c, 0xf8, 0x46, 0xe1, 0x39, 0x6f, 0xac, 0x0d,
2796 0x21, 0xf2, 0xc1, 0xc5, 0xd8, 0x6f, 0xac, 0x0d, 0x15, 0xec, 0x3c, 0xaa, 0x76, 0x70,
2797 0xac, 0x0d, 0x0a, 0xe6, 0xb7, 0x8e, 0x14, 0x71, 0xac, 0x0d, 0xff, 0xdf, 0x32, 0x73,
2798 0xb2, 0x71, 0xac, 0x0d, 0x3d, 0xe1, 0xff, 0x5f, 0x8d, 0x73, 0xac, 0x0d, 0x7c, 0xe2,
2799 0xcc, 0x4c, 0x67, 0x75, 0xac, 0x0d, 0xba, 0xe3, 0x99, 0x39, 0x42, 0x77, 0xac, 0x0d,
2800 0xf9, 0xe4, 0x66, 0x26, 0x1c, 0x79, 0xac, 0x0d, 0x4e, 0xe2, 0x0a, 0x27, 0xbb, 0x79,
2801 0xac, 0x0d, 0xa4, 0xdf, 0xad, 0x27, 0x59, 0x7a, 0xac, 0x0d, 0xf9, 0xdc, 0x51, 0x28,
2802 0xf7, 0x7a, 0xac, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2803 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2804 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2805 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2806 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2807 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2808 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2809 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2810 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2811 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2812 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2813 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2814 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2815 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2816 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2817 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2818 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2819 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2820 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2821 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2822 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2823 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2824 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2825 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2826 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2827 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2828 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2829 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2830 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2831 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2832 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2833 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2834 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2835 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2836 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2837 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2838 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2839 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2840 ];
2841 let rawmsg = RawMessage::unpack(&v).unwrap();
2842 rawmsg.validate();
2843
2844 let msg = Message::from_raw(&rawmsg).unwrap();
2845
2846 match msg {
2847 Message::SetExtendedColorZones {
2848 duration: 1300,
2849 apply: ApplicationRequest::Apply,
2850 zone_index: 0,
2851 colors_count: 16,
2852 colors,
2853 } => {
2854 assert_eq!(colors.len(), 82);
2855 }
2856 _ => {
2857 panic!("Unexpected message")
2858 }
2859 }
2860 }
2861
2862 #[test]
2863 fn test_lifx_decode_setmultizoneeffect_message() {
2864 let v = vec![
2865 0x5f, 0x00, 0x00, 0x14, 0x10, 0x00, 0x3e, 0x8f, 0xd0, 0x73, 0xd5, 0x6f, 0x20, 0xad,
2866 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x9a, 0x00, 0x00, 0x00, 0x00,
2867 0x00, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
2868 0x00, 0xb8, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2869 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2870 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2871 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2872 ];
2873 let rawmsg = RawMessage::unpack(&v).unwrap();
2874 rawmsg.validate();
2875
2876 let msg = Message::from_raw(&rawmsg).unwrap();
2877
2878 assert!(
2879 msg == Message::SetMultiZoneEffect {
2880 instance_id: 0,
2881 typ: MultiZoneEffectType::Move,
2882 reserved: 0,
2883 speed: 3000,
2884 duration: 0,
2885 reserved7: 0,
2886 reserved8: 0,
2887 parameters: [0, 0, 1, 0, 0, 0, 0, 0,],
2888 }
2889 )
2890 }
2891}