1use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
27use thiserror::Error;
28use std::convert::{TryFrom, TryInto};
29use std::io::Cursor;
30use std::io;
31
32
33use serde::{Serialize, Deserialize};
34
35#[derive(Error, Debug)]
37pub enum Error {
38 #[error("unknown message type: `{0}`")]
43 UnknownMessageType(u16),
44 #[error("protocol error: `{0}`")]
46 ProtocolError(String),
47
48 #[error("i/o error")]
49 Io(#[from] io::Error),
50}
51
52impl From<std::convert::Infallible> for Error {
53 fn from(_: std::convert::Infallible) -> Self {
54 unreachable!()
55 }
56}
57
58impl TryFrom<u8> for ApplicationRequest {
59 type Error = Error;
60 fn try_from(val: u8) -> Result<ApplicationRequest, Error> {
61 match val {
62 0 => Ok(ApplicationRequest::NoApply),
63 1 => Ok(ApplicationRequest::Apply),
64 2 => Ok(ApplicationRequest::ApplyOnly),
65 x => Err(Error::ProtocolError(format!(
66 "Unknown application request {}",
67 x
68 ))),
69 }
70 }
71}
72
73impl TryFrom<u8> for Waveform {
74 type Error = Error;
75 fn try_from(val: u8) -> Result<Waveform, Error> {
76 match val {
77 0 => Ok(Waveform::Saw),
78 1 => Ok(Waveform::Sine),
79 2 => Ok(Waveform::HalfSign),
80 3 => Ok(Waveform::Triangle),
81 4 => Ok(Waveform::Pulse),
82 x => Err(Error::ProtocolError(format!(
83 "Unknown waveform value {}",
84 x
85 ))),
86 }
87 }
88}
89
90impl TryFrom<u8> for Service {
91 type Error = Error;
92 fn try_from(val: u8) -> Result<Service, Error> {
93 if val != Service::UDP as u8 {
94 Err(Error::ProtocolError(format!(
95 "Unknown service value {}",
96 val
97 )))
98 } else {
99 Ok(Service::UDP)
100 }
101 }
102}
103
104impl TryFrom<u16> for PowerLevel {
105 type Error = Error;
106 fn try_from(val: u16) -> Result<PowerLevel, Error> {
107 match val {
108 x if x == PowerLevel::Enabled as u16 => Ok(PowerLevel::Enabled),
109 x if x == PowerLevel::Standby as u16 => Ok(PowerLevel::Standby),
110 x => Err(Error::ProtocolError(format!("Unknown power level {}", x))),
111 }
112 }
113}
114
115#[derive(Copy, Clone)]
116pub struct EchoPayload(pub [u8; 64]);
117
118impl std::fmt::Debug for EchoPayload {
119 fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
120 write!(f, "<EchoPayload>")
121 }
122}
123
124#[derive(Debug, Clone, PartialEq)]
125pub struct LifxIdent(pub [u8; 16]);
126
127#[derive(Debug, Clone, PartialEq)]
129pub struct LifxString(pub String);
130
131impl LifxString {
132 pub fn new(s: &str) -> LifxString {
134 LifxString(if s.len() > 32 {
135 s[..32].to_owned()
136 } else {
137 s.to_owned()
138 })
139 }
140}
141
142impl std::fmt::Display for LifxString {
143 fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
144 write!(fmt, "{}", self.0)
145 }
146}
147
148impl std::cmp::PartialEq<str> for LifxString {
149 fn eq(&self, other: &str) -> bool {
150 self.0 == other
151 }
152}
153
154trait LittleEndianWriter<T>: WriteBytesExt {
155 fn write_val(&mut self, v: T) -> Result<(), io::Error>;
156}
157
158macro_rules! derive_writer {
159{ $( $m:ident: $t:ty ),*} => {
160 $(
161 impl<T: WriteBytesExt> LittleEndianWriter<$t> for T {
162 fn write_val(&mut self, v: $t) -> Result<(), io::Error> {
163 self . $m ::<LittleEndian>(v)
164 }
165 }
166 )*
167
168}
169}
170
171derive_writer! { write_u32: u32, write_u16: u16, write_i16: i16, write_u64: u64, write_f32: f32 }
172
173impl<T: WriteBytesExt> LittleEndianWriter<u8> for T {
174 fn write_val(&mut self, v: u8) -> Result<(), io::Error> {
175 self.write_u8(v)
176 }
177}
178
179impl<T: WriteBytesExt> LittleEndianWriter<bool> for T {
180 fn write_val(&mut self, v: bool) -> Result<(), io::Error> {
181 self.write_u8(if v { 1 } else { 0 })
182 }
183}
184
185impl<T> LittleEndianWriter<LifxString> for T
186where
187 T: WriteBytesExt,
188{
189 fn write_val(&mut self, v: LifxString) -> Result<(), io::Error> {
190 for idx in 0..32 {
191 if idx >= v.0.len() {
192 self.write_u8(0)?;
193 } else {
194 self.write_u8(v.0.chars().nth(idx).unwrap() as u8)?;
195 }
196 }
197 Ok(())
198 }
199}
200
201impl<T> LittleEndianWriter<LifxIdent> for T
202where
203 T: WriteBytesExt,
204{
205 fn write_val(&mut self, v: LifxIdent) -> Result<(), io::Error> {
206 for idx in 0..16 {
207 self.write_u8(v.0[idx])?;
208 }
209 Ok(())
210 }
211}
212
213impl<T> LittleEndianWriter<EchoPayload> for T
214where
215 T: WriteBytesExt,
216{
217 fn write_val(&mut self, v: EchoPayload) -> Result<(), io::Error> {
218 for idx in 0..64 {
219 self.write_u8(v.0[idx])?;
220 }
221 Ok(())
222 }
223}
224
225impl<T> LittleEndianWriter<HSBK> for T
226where
227 T: WriteBytesExt,
228{
229 fn write_val(&mut self, v: HSBK) -> Result<(), io::Error> {
230 self.write_val(v.hue)?;
231 self.write_val(v.saturation)?;
232 self.write_val(v.brightness)?;
233 self.write_val(v.kelvin)?;
234 Ok(())
235 }
236}
237
238impl<T> LittleEndianWriter<PowerLevel> for T
239where
240 T: WriteBytesExt,
241{
242 fn write_val(&mut self, v: PowerLevel) -> Result<(), io::Error> {
243 self.write_u16::<LittleEndian>(v as u16)
244 }
245}
246
247impl<T> LittleEndianWriter<ApplicationRequest> for T
248where
249 T: WriteBytesExt,
250{
251 fn write_val(&mut self, v: ApplicationRequest) -> Result<(), io::Error> {
252 self.write_u8(v as u8)
253 }
254}
255
256impl<T> LittleEndianWriter<Waveform> for T
257where
258 T: WriteBytesExt,
259{
260 fn write_val(&mut self, v: Waveform) -> Result<(), io::Error> {
261 self.write_u8(v as u8)
262 }
263}
264
265trait LittleEndianReader<T> {
266 fn read_val(&mut self) -> Result<T, io::Error>;
267}
268
269macro_rules! derive_reader {
270{ $( $m:ident: $t:ty ),*} => {
271 $(
272 impl<T: ReadBytesExt> LittleEndianReader<$t> for T {
273 fn read_val(&mut self) -> Result<$t, io::Error> {
274 self . $m ::<LittleEndian>()
275 }
276 }
277 )*
278
279}
280}
281
282derive_reader! { read_u32: u32, read_u16: u16, read_i16: i16, read_u64: u64, read_f32: f32 }
283
284impl<R: ReadBytesExt> LittleEndianReader<u8> for R {
285 fn read_val(&mut self) -> Result<u8, io::Error> {
286 self.read_u8()
287 }
288}
289
290impl<R: ReadBytesExt> LittleEndianReader<HSBK> for R {
291 fn read_val(&mut self) -> Result<HSBK, io::Error> {
292 let hue = self.read_val()?;
293 let sat = self.read_val()?;
294 let bri = self.read_val()?;
295 let kel = self.read_val()?;
296 Ok(HSBK {
297 hue,
298 saturation: sat,
299 brightness: bri,
300 kelvin: kel,
301 })
302 }
303}
304
305impl<R: ReadBytesExt> LittleEndianReader<LifxIdent> for R {
306 fn read_val(&mut self) -> Result<LifxIdent, io::Error> {
307 let mut val = [0; 16];
308 for v in &mut val {
309 *v = self.read_val()?;
310 }
311 Ok(LifxIdent(val))
312 }
313}
314
315impl<R: ReadBytesExt> LittleEndianReader<LifxString> for R {
316 fn read_val(&mut self) -> Result<LifxString, io::Error> {
317 let mut label = String::with_capacity(32);
318 for _ in 0..32 {
319 let c: u8 = self.read_val()?;
320 if c > 0 {
321 label.push(c as char);
322 }
323 }
324 Ok(LifxString(label))
325 }
326}
327
328impl<R: ReadBytesExt> LittleEndianReader<EchoPayload> for R {
329 fn read_val(&mut self) -> Result<EchoPayload, io::Error> {
330 let mut val = [0; 64];
331 for v in val.iter_mut() {
332 *v = self.read_val()?;
333 }
334 Ok(EchoPayload(val))
335 }
336}
337
338macro_rules! unpack {
339 ($msg:ident, $typ:ident, $( $n:ident: $t:ident ),*) => {
340 {
341 let mut c = Cursor::new(&$msg.payload);
342 $(
343 let $n: $t = c.read_val()?;
344 )*
345
346 Message::$typ {
347 $(
348 $n: $n.try_into()?,
349 )*
350 }
351 }
352 };
353}
354
355#[repr(u8)]
378#[derive(Debug, Copy, Clone, PartialEq)]
379pub enum Service {
380 UDP = 1,
381}
382
383#[repr(u16)]
384#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
385pub enum PowerLevel {
386 Standby = 0,
387 Enabled = 65535,
388}
389
390#[repr(u8)]
394#[derive(Debug, Copy, Clone)]
395pub enum ApplicationRequest {
396 NoApply = 0,
398 Apply = 1,
400 ApplyOnly = 2,
402}
403
404#[repr(u8)]
405#[derive(Debug, Copy, Clone)]
406pub enum Waveform {
407 Saw = 0,
408 Sine = 1,
409 HalfSign = 2,
410 Triangle = 3,
411 Pulse = 4,
412}
413
414#[derive(Clone, Debug)]
421pub enum Message {
422 GetService,
427
428 StateService {
432 port: u32,
435 service: Service,
437 },
438
439 GetHostInfo,
444
445 StateHostInfo {
451 signal: f32,
453 tx: u32,
455 rx: u32,
457 reserved: i16,
458 },
459
460 GetHostFirmware,
465
466 StateHostFirmware {
472 build: u64,
474 reserved: u64,
475 version: u32,
477 },
478
479 GetWifiInfo,
484
485 StateWifiInfo {
491 signal: f32,
493 tx: u32,
495 rx: u32,
497 reserved: i16,
498 },
499
500 GetWifiFirmware,
505
506 StateWifiFirmware {
512 build: u64,
514 reserved: u64,
515 version: u32,
517 },
518
519 GetPower,
524
525 SetPower {
529 level: PowerLevel,
533 },
534
535 StatePower { level: PowerLevel },
541
542 GetLabel,
547
548 SetLabel { label: LifxString },
552
553 StateLabel { label: LifxString },
559
560 GetVersion,
565
566 StateVersion {
572 vendor: u32,
574 product: u32,
576 version: u32,
578 },
579
580 GetInfo,
585
586 StateInfo {
592 time: u64,
594 uptime: u64,
596 downtime: u64,
598 },
599
600 Acknowledgement { seq: u8 },
607
608 GetLocation,
613
614 SetLocation {
618 location: LifxIdent,
620 label: LifxString,
622 updated_at: u64,
624 },
625
626 StateLocation {
630 location: LifxIdent,
631 label: LifxString,
632 updated_at: u64,
633 },
634
635 GetGroup,
641
642 SetGroup {
646 group: LifxIdent,
647 label: LifxString,
648 updated_at: u64,
649 },
650
651 StateGroup {
655 group: LifxIdent,
656 label: LifxString,
657 updated_at: u64,
658 },
659
660 EchoRequest { payload: EchoPayload },
665
666 EchoResponse { payload: EchoPayload },
673
674 LightGet,
679
680 LightSetColor {
687 reserved: u8,
688 color: HSBK,
690 duration: u32,
692 },
693
694 SetWaveform {
698 reserved: u8,
699 transient: bool,
700 color: HSBK,
701 period: u32,
703 cycles: f32,
705 skew_ratio: i16,
707 waveform: Waveform,
709 },
710
711 LightState {
715 color: HSBK,
716 reserved: i16,
717 power: PowerLevel,
718 label: LifxString,
719 reserved2: u64,
720 },
721
722 LightGetPower,
727
728 LightSetPower { level: u16, duration: u32 },
742
743 LightStatePower { level: u16 },
750
751 SetWaveformOptional {
755 reserved: u8,
756 transient: bool,
757 color: HSBK,
758 period: u32,
760 cycles: f32,
762
763 skew_ratio: i16,
764 waveform: Waveform,
765 set_hue: bool,
766 set_saturation: bool,
767 set_brightness: bool,
768 set_kelvin: bool,
769 },
770
771 LightGetInfrared,
775
776 LightStateInfrared { brightness: u16 },
780
781 LightSetInfrared { brightness: u16 },
785
786 SetColorZones {
792 start_index: u8,
793 end_index: u8,
794 color: HSBK,
795 duration: u32,
796 apply: ApplicationRequest,
797 },
798
799 GetColorZones { start_index: u8, end_index: u8 },
807
808 StateZone { count: u8, index: u8, color: HSBK },
814
815 StateMultiZone {
823 count: u8,
824 index: u8,
825 color0: HSBK,
826 color1: HSBK,
827 color2: HSBK,
828 color3: HSBK,
829 color4: HSBK,
830 color5: HSBK,
831 color6: HSBK,
832 color7: HSBK,
833 },
834}
835
836impl Message {
837 pub fn get_num(&self) -> u16 {
838 match *self {
839 Message::GetService => 2,
840 Message::StateService { .. } => 3,
841 Message::GetHostInfo => 12,
842 Message::StateHostInfo { .. } => 13,
843 Message::GetHostFirmware => 14,
844 Message::StateHostFirmware { .. } => 15,
845 Message::GetWifiInfo => 16,
846 Message::StateWifiInfo { .. } => 17,
847 Message::GetWifiFirmware => 18,
848 Message::StateWifiFirmware { .. } => 19,
849 Message::GetPower => 20,
850 Message::SetPower { .. } => 21,
851 Message::StatePower { .. } => 22,
852 Message::GetLabel => 23,
853 Message::SetLabel { .. } => 24,
854 Message::StateLabel { .. } => 25,
855 Message::GetVersion => 32,
856 Message::StateVersion { .. } => 33,
857 Message::GetInfo => 34,
858 Message::StateInfo { .. } => 35,
859 Message::Acknowledgement { .. } => 45,
860 Message::GetLocation => 48,
861 Message::SetLocation { .. } => 49,
862 Message::StateLocation { .. } => 50,
863 Message::GetGroup => 51,
864 Message::SetGroup { .. } => 52,
865 Message::StateGroup { .. } => 53,
866 Message::EchoRequest { .. } => 58,
867 Message::EchoResponse { .. } => 59,
868 Message::LightGet => 101,
869 Message::LightSetColor { .. } => 102,
870 Message::SetWaveform { .. } => 103,
871 Message::LightState { .. } => 107,
872 Message::LightGetPower => 116,
873 Message::LightSetPower { .. } => 117,
874 Message::LightStatePower { .. } => 118,
875 Message::SetWaveformOptional { .. } => 119,
876 Message::LightGetInfrared => 120,
877 Message::LightStateInfrared { .. } => 121,
878 Message::LightSetInfrared { .. } => 122,
879 Message::SetColorZones { .. } => 501,
880 Message::GetColorZones { .. } => 502,
881 Message::StateZone { .. } => 503,
882 Message::StateMultiZone { .. } => 506,
883 }
884 }
885
886 pub fn from_raw(msg: &RawMessage) -> Result<Message, Error> {
888 match msg.protocol_header.typ {
889 2 => Ok(Message::GetService),
890 3 => Ok(unpack!(msg, StateService, service: u8, port: u32)),
891 12 => Ok(Message::GetHostInfo),
892 13 => Ok(unpack!(
893 msg,
894 StateHostInfo,
895 signal: f32,
896 tx: u32,
897 rx: u32,
898 reserved: i16
899 )),
900 14 => Ok(Message::GetHostFirmware),
901 15 => Ok(unpack!(
902 msg,
903 StateHostFirmware,
904 build: u64,
905 reserved: u64,
906 version: u32
907 )),
908 16 => Ok(Message::GetWifiInfo),
909 17 => Ok(unpack!(
910 msg,
911 StateWifiInfo,
912 signal: f32,
913 tx: u32,
914 rx: u32,
915 reserved: i16
916 )),
917 18 => Ok(Message::GetWifiFirmware),
918 19 => Ok(unpack!(
919 msg,
920 StateWifiFirmware,
921 build: u64,
922 reserved: u64,
923 version: u32
924 )),
925 20 => Ok(Message::GetPower),
926 22 => Ok(unpack!(msg, StatePower, level: u16)),
927 23 => Ok(Message::GetLabel),
928 25 => Ok(unpack!(msg, StateLabel, label: LifxString)),
929 32 => Ok(Message::GetVersion),
930 33 => Ok(unpack!(
931 msg,
932 StateVersion,
933 vendor: u32,
934 product: u32,
935 version: u32
936 )),
937 35 => Ok(unpack!(
938 msg,
939 StateInfo,
940 time: u64,
941 uptime: u64,
942 downtime: u64
943 )),
944 45 => Ok(Message::Acknowledgement {
945 seq: msg.frame_addr.sequence,
946 }),
947 48 => Ok(Message::GetLocation),
948 50 => Ok(unpack!(
949 msg,
950 StateLocation,
951 location: LifxIdent,
952 label: LifxString,
953 updated_at: u64
954 )),
955 51 => Ok(Message::GetGroup),
956 53 => Ok(unpack!(
957 msg,
958 StateGroup,
959 group: LifxIdent,
960 label: LifxString,
961 updated_at: u64
962 )),
963 58 => Ok(unpack!(msg, EchoRequest, payload: EchoPayload)),
964 59 => Ok(unpack!(msg, EchoResponse, payload: EchoPayload)),
965 101 => Ok(Message::LightGet),
966 102 => Ok(unpack!(
967 msg,
968 LightSetColor,
969 reserved: u8,
970 color: HSBK,
971 duration: u32
972 )),
973 107 => Ok(unpack!(
974 msg,
975 LightState,
976 color: HSBK,
977 reserved: i16,
978 power: u16,
979 label: LifxString,
980 reserved2: u64
981 )),
982 116 => Ok(Message::LightGetPower),
983 117 => Ok(unpack!(msg, LightSetPower, level: u16, duration: u32)),
984 118 => {
985 let mut c = Cursor::new(&msg.payload);
986 Ok(Message::LightStatePower {
987 level: c.read_val()?,
988 })
989 }
990 121 => Ok(unpack!(msg, LightStateInfrared, brightness: u16)),
991 501 => Ok(unpack!(
992 msg,
993 SetColorZones,
994 start_index: u8,
995 end_index: u8,
996 color: HSBK,
997 duration: u32,
998 apply: u8
999 )),
1000 502 => Ok(unpack!(msg, GetColorZones, start_index: u8, end_index: u8)),
1001 503 => Ok(unpack!(msg, StateZone, count: u8, index: u8, color: HSBK)),
1002 506 => Ok(unpack!(
1003 msg,
1004 StateMultiZone,
1005 count: u8,
1006 index: u8,
1007 color0: HSBK,
1008 color1: HSBK,
1009 color2: HSBK,
1010 color3: HSBK,
1011 color4: HSBK,
1012 color5: HSBK,
1013 color6: HSBK,
1014 color7: HSBK
1015 )),
1016 _ => Err(Error::UnknownMessageType(msg.protocol_header.typ)),
1017 }
1018 }
1019}
1020
1021#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
1036pub struct HSBK {
1037 pub hue: u16,
1038 pub saturation: u16,
1039 pub brightness: u16,
1040 pub kelvin: u16,
1041}
1042
1043impl HSBK {
1044 pub fn describe(&self, short: bool) -> String {
1045 match short {
1046 true if self.saturation == 0 => format!("{}K", self.kelvin),
1047 true => format!(
1048 "{:.0}/{:.0}",
1049 (self.hue as f32 / 65535.0) * 360.0,
1050 self.saturation as f32 / 655.35
1051 ),
1052 false if self.saturation == 0 => format!(
1053 "{:.0}% White ({})",
1054 self.brightness as f32 / 655.35,
1055 describe_kelvin(self.kelvin)
1056 ),
1057 false => format!(
1058 "{}% hue: {} sat: {}",
1059 self.brightness as f32 / 655.35,
1060 self.hue,
1061 self.saturation
1062 ),
1063 }
1064 }
1065}
1066
1067pub fn describe_kelvin(k: u16) -> &'static str {
1071 if k <= 2500 {
1072 "Ultra Warm"
1073 } else if k > 2500 && k <= 2700 {
1074 "Incandescent"
1075 } else if k > 2700 && k <= 3000 {
1076 "Warm"
1077 } else if k > 300 && k <= 3200 {
1078 "Neutral Warm"
1079 } else if k > 3200 && k <= 3500 {
1080 "Neutral"
1081 } else if k > 3500 && k <= 4000 {
1082 "Cool"
1083 } else if k > 400 && k <= 4500 {
1084 "Cool Daylight"
1085 } else if k > 4500 && k <= 5000 {
1086 "Soft Daylight"
1087 } else if k > 5000 && k <= 5500 {
1088 "Daylight"
1089 } else if k > 5500 && k <= 6000 {
1090 "Noon Daylight"
1091 } else if k > 6000 && k <= 6500 {
1092 "Bright Daylight"
1093 } else if k > 6500 && k <= 7000 {
1094 "Cloudy Daylight"
1095 } else if k > 7000 && k <= 7500 {
1096 "Blue Daylight"
1097 } else if k > 7500 && k <= 8000 {
1098 "Blue Overcast"
1099 } else if k > 8000 && k <= 8500 {
1100 "Blue Water"
1101 } else {
1102 "Blue Ice"
1103 }
1104}
1105
1106impl HSBK {}
1107
1108#[derive(Debug, Clone, PartialEq)]
1114pub struct RawMessage {
1115 pub frame: Frame,
1116 pub frame_addr: FrameAddress,
1117 pub protocol_header: ProtocolHeader,
1118 pub payload: Vec<u8>,
1119}
1120
1121#[derive(Debug, Clone, Copy, PartialEq)]
1132pub struct Frame {
1133 pub size: u16,
1135
1136 pub origin: u8,
1138
1139 pub tagged: bool,
1141
1142 pub addressable: bool,
1144
1145 pub protocol: u16,
1147
1148 pub source: u32,
1156}
1157
1158#[derive(Debug, Clone, Copy, PartialEq)]
1165pub struct FrameAddress {
1166 pub target: u64,
1168
1169 pub reserved: [u8; 6],
1171
1172 pub reserved2: u8,
1174
1175 pub ack_required: bool,
1177
1178 pub res_required: bool,
1180
1181 pub sequence: u8,
1183}
1184
1185#[derive(Debug, Clone, Copy, PartialEq)]
1186pub struct ProtocolHeader {
1187 pub reserved: u64,
1189
1190 pub typ: u16,
1192
1193 pub reserved2: u16,
1195}
1196
1197impl Frame {
1198 fn packed_size() -> usize {
1200 8
1201 }
1202
1203 fn validate(&self) {
1204 assert!(self.origin < 4);
1205 assert_eq!(self.addressable, true);
1206 assert_eq!(self.protocol, 1024);
1207 }
1208 fn pack(&self) -> Result<Vec<u8>, Error> {
1209 let mut v = Vec::with_capacity(Self::packed_size());
1210
1211 v.write_u16::<LittleEndian>(self.size)?;
1212
1213 let mut d: u16 = (<u16 as From<u8>>::from(self.origin) & 0b11) << 14;
1215 d += if self.tagged { 1 } else { 0 } << 13;
1216 d += if self.addressable { 1 } else { 0 } << 12;
1217 d += (self.protocol & 0b1111_1111_1111) as u16;
1218
1219 v.write_u16::<LittleEndian>(d)?;
1220
1221 v.write_u32::<LittleEndian>(self.source)?;
1222
1223 Ok(v)
1224 }
1225 fn unpack(v: &[u8]) -> Result<Frame, Error> {
1226 let mut c = Cursor::new(v);
1227
1228 let size = c.read_val()?;
1229
1230 let d: u16 = c.read_val()?;
1232
1233 let origin: u8 = ((d & 0b1100_0000_0000_0000) >> 14) as u8;
1234 let tagged: bool = (d & 0b0010_0000_0000_0000) > 0;
1235 let addressable = (d & 0b0001_0000_0000_0000) > 0;
1236 let protocol: u16 = d & 0b0000_1111_1111_1111;
1237
1238 if protocol != 1024 {
1239 return Err(Error::ProtocolError(format!(
1240 "Unpacked frame had protocol version {}",
1241 protocol
1242 )));
1243 }
1244
1245 let source = c.read_val()?;
1246
1247 let frame = Frame {
1248 size,
1249 origin,
1250 tagged,
1251 addressable,
1252 protocol,
1253 source,
1254 };
1255 Ok(frame)
1256 }
1257}
1258
1259impl FrameAddress {
1260 fn packed_size() -> usize {
1261 16
1262 }
1263 fn validate(&self) {
1264 }
1267 fn pack(&self) -> Result<Vec<u8>, Error> {
1268 let mut v = Vec::with_capacity(Self::packed_size());
1269 v.write_u64::<LittleEndian>(self.target)?;
1270 for idx in 0..6 {
1271 v.write_u8(self.reserved[idx])?;
1272 }
1273
1274 let b: u8 = (self.reserved2 << 2)
1275 + if self.ack_required { 2 } else { 0 }
1276 + if self.res_required { 1 } else { 0 };
1277 v.write_u8(b)?;
1278 v.write_u8(self.sequence)?;
1279 Ok(v)
1280 }
1281
1282 fn unpack(v: &[u8]) -> Result<FrameAddress, Error> {
1283 let mut c = Cursor::new(v);
1284
1285 let target = c.read_val()?;
1286
1287 let mut reserved: [u8; 6] = [0; 6];
1288 for slot in &mut reserved {
1289 *slot = c.read_val()?;
1290 }
1291
1292 let b: u8 = c.read_val()?;
1293 let reserved2: u8 = (b & 0b1111_1100) >> 2;
1294 let ack_required = (b & 0b10) > 0;
1295 let res_required = (b & 0b01) > 0;
1296
1297 let sequence = c.read_val()?;
1298
1299 let f = FrameAddress {
1300 target,
1301 reserved,
1302 reserved2,
1303 ack_required,
1304 res_required,
1305 sequence,
1306 };
1307 f.validate();
1308 Ok(f)
1309 }
1310}
1311
1312impl ProtocolHeader {
1313 fn packed_size() -> usize {
1314 12
1315 }
1316 fn validate(&self) {
1317 }
1320
1321 pub fn pack(&self) -> Result<Vec<u8>, Error> {
1323 let mut v = Vec::with_capacity(Self::packed_size());
1324 v.write_u64::<LittleEndian>(self.reserved)?;
1325 v.write_u16::<LittleEndian>(self.typ)?;
1326 v.write_u16::<LittleEndian>(self.reserved2)?;
1327 Ok(v)
1328 }
1329 fn unpack(v: &[u8]) -> Result<ProtocolHeader, Error> {
1330 let mut c = Cursor::new(v);
1331
1332 let reserved = c.read_val()?;
1333 let typ = c.read_val()?;
1334 let reserved2 = c.read_val()?;
1335
1336 let f = ProtocolHeader {
1337 reserved,
1338 typ,
1339 reserved2,
1340 };
1341 f.validate();
1342 Ok(f)
1343 }
1344}
1345
1346#[derive(Debug, Clone)]
1350pub struct BuildOptions {
1351 pub target: Option<u64>,
1356 pub ack_required: bool,
1360 pub res_required: bool,
1365 pub sequence: u8,
1371 pub source: u32,
1377}
1378
1379impl std::default::Default for BuildOptions {
1380 fn default() -> BuildOptions {
1381 BuildOptions {
1382 target: None,
1383 ack_required: false,
1384 res_required: false,
1385 sequence: 0,
1386 source: 0,
1387 }
1388 }
1389}
1390
1391impl RawMessage {
1392 pub fn build(options: &BuildOptions, typ: Message) -> Result<RawMessage, Error> {
1398 let frame = Frame {
1399 size: 0,
1400 origin: 0,
1401 tagged: options.target.is_none(),
1402 addressable: true,
1403 protocol: 1024,
1404 source: options.source,
1405 };
1406 let addr = FrameAddress {
1407 target: options.target.unwrap_or(0),
1408 reserved: [0; 6],
1409 reserved2: 0,
1410 ack_required: options.ack_required,
1411 res_required: options.res_required,
1412 sequence: options.sequence,
1413 };
1414 let phead = ProtocolHeader {
1415 reserved: 0,
1416 reserved2: 0,
1417 typ: typ.get_num(),
1418 };
1419
1420 let mut v = Vec::new();
1421 match typ {
1422 Message::GetService
1423 | Message::GetHostInfo
1424 | Message::GetHostFirmware
1425 | Message::GetWifiFirmware
1426 | Message::GetWifiInfo
1427 | Message::GetPower
1428 | Message::GetLabel
1429 | Message::GetVersion
1430 | Message::GetInfo
1431 | Message::Acknowledgement { .. }
1432 | Message::GetLocation
1433 | Message::GetGroup
1434 | Message::LightGet
1435 | Message::LightGetPower
1436 | Message::LightGetInfrared => {
1437 }
1439 Message::SetColorZones {
1440 start_index,
1441 end_index,
1442 color,
1443 duration,
1444 apply,
1445 } => {
1446 v.write_val(start_index)?;
1447 v.write_val(end_index)?;
1448 v.write_val(color)?;
1449 v.write_val(duration)?;
1450 v.write_val(apply)?;
1451 }
1452 Message::SetWaveform {
1453 reserved,
1454 transient,
1455 color,
1456 period,
1457 cycles,
1458 skew_ratio,
1459 waveform,
1460 } => {
1461 v.write_val(reserved)?;
1462 v.write_val(transient)?;
1463 v.write_val(color)?;
1464 v.write_val(period)?;
1465 v.write_val(cycles)?;
1466 v.write_val(skew_ratio)?;
1467 v.write_val(waveform)?;
1468 }
1469 Message::SetWaveformOptional {
1470 reserved,
1471 transient,
1472 color,
1473 period,
1474 cycles,
1475 skew_ratio,
1476 waveform,
1477 set_hue,
1478 set_saturation,
1479 set_brightness,
1480 set_kelvin,
1481 } => {
1482 v.write_val(reserved)?;
1483 v.write_val(transient)?;
1484 v.write_val(color)?;
1485 v.write_val(period)?;
1486 v.write_val(cycles)?;
1487 v.write_val(skew_ratio)?;
1488 v.write_val(waveform)?;
1489 v.write_val(set_hue)?;
1490 v.write_val(set_saturation)?;
1491 v.write_val(set_brightness)?;
1492 v.write_val(set_kelvin)?;
1493 }
1494 Message::GetColorZones {
1495 start_index,
1496 end_index,
1497 } => {
1498 v.write_val(start_index)?;
1499 v.write_val(end_index)?;
1500 }
1501 Message::StateZone {
1502 count,
1503 index,
1504 color,
1505 } => {
1506 v.write_val(count)?;
1507 v.write_val(index)?;
1508 v.write_val(color)?;
1509 }
1510 Message::StateMultiZone {
1511 count,
1512 index,
1513 color0,
1514 color1,
1515 color2,
1516 color3,
1517 color4,
1518 color5,
1519 color6,
1520 color7,
1521 } => {
1522 v.write_val(count)?;
1523 v.write_val(index)?;
1524 v.write_val(color0)?;
1525 v.write_val(color1)?;
1526 v.write_val(color2)?;
1527 v.write_val(color3)?;
1528 v.write_val(color4)?;
1529 v.write_val(color5)?;
1530 v.write_val(color6)?;
1531 v.write_val(color7)?;
1532 }
1533 Message::LightStateInfrared { brightness } => v.write_val(brightness)?,
1534 Message::LightSetInfrared { brightness } => v.write_val(brightness)?,
1535 Message::SetLocation {
1536 location,
1537 label,
1538 updated_at,
1539 } => {
1540 v.write_val(location)?;
1541 v.write_val(label)?;
1542 v.write_val(updated_at)?;
1543 }
1544 Message::SetGroup {
1545 group,
1546 label,
1547 updated_at,
1548 } => {
1549 v.write_val(group)?;
1550 v.write_val(label)?;
1551 v.write_val(updated_at)?;
1552 }
1553 Message::StateService { port, service } => {
1554 v.write_val(port)?;
1555 v.write_val(service as u8)?;
1556 }
1557 Message::StateHostInfo {
1558 signal,
1559 tx,
1560 rx,
1561 reserved,
1562 } => {
1563 v.write_val(signal)?;
1564 v.write_val(tx)?;
1565 v.write_val(rx)?;
1566 v.write_val(reserved)?;
1567 }
1568 Message::StateHostFirmware {
1569 build,
1570 reserved,
1571 version,
1572 } => {
1573 v.write_val(build)?;
1574 v.write_val(reserved)?;
1575 v.write_val(version)?;
1576 }
1577 Message::StateWifiInfo {
1578 signal,
1579 tx,
1580 rx,
1581 reserved,
1582 } => {
1583 v.write_val(signal)?;
1584 v.write_val(tx)?;
1585 v.write_val(rx)?;
1586 v.write_val(reserved)?;
1587 }
1588 Message::StateWifiFirmware {
1589 build,
1590 reserved,
1591 version,
1592 } => {
1593 v.write_val(build)?;
1594 v.write_val(reserved)?;
1595 v.write_val(version)?;
1596 }
1597 Message::SetPower { level } => {
1598 v.write_val(level)?;
1599 }
1600 Message::StatePower { level } => {
1601 v.write_val(level)?;
1602 }
1603 Message::SetLabel { label } => {
1604 v.write_val(label)?;
1605 }
1606 Message::StateLabel { label } => {
1607 v.write_val(label)?;
1608 }
1609 Message::StateVersion {
1610 vendor,
1611 product,
1612 version,
1613 } => {
1614 v.write_val(vendor)?;
1615 v.write_val(product)?;
1616 v.write_val(version)?;
1617 }
1618 Message::StateInfo {
1619 time,
1620 uptime,
1621 downtime,
1622 } => {
1623 v.write_val(time)?;
1624 v.write_val(uptime)?;
1625 v.write_val(downtime)?;
1626 }
1627 Message::StateLocation {
1628 location,
1629 label,
1630 updated_at,
1631 } => {
1632 v.write_val(location)?;
1633 v.write_val(label)?;
1634 v.write_val(updated_at)?;
1635 }
1636 Message::StateGroup {
1637 group,
1638 label,
1639 updated_at,
1640 } => {
1641 v.write_val(group)?;
1642 v.write_val(label)?;
1643 v.write_val(updated_at)?;
1644 }
1645 Message::EchoRequest { payload } => {
1646 v.write_val(payload)?;
1647 }
1648 Message::EchoResponse { payload } => {
1649 v.write_val(payload)?;
1650 }
1651 Message::LightSetColor {
1652 reserved,
1653 color,
1654 duration,
1655 } => {
1656 v.write_val(reserved)?;
1657 v.write_val(color)?;
1658 v.write_val(duration)?;
1659 }
1660 Message::LightState {
1661 color,
1662 reserved,
1663 power,
1664 label,
1665 reserved2,
1666 } => {
1667 v.write_val(color)?;
1668 v.write_val(reserved)?;
1669 v.write_val(power)?;
1670 v.write_val(label)?;
1671 v.write_val(reserved2)?;
1672 }
1673 Message::LightSetPower { level, duration } => {
1674 v.write_val(if level > 0 { 65535u16 } else { 0u16 })?;
1675 v.write_val(duration)?;
1676 }
1677 Message::LightStatePower { level } => {
1678 v.write_val(level)?;
1679 }
1680 }
1681
1682 let mut msg = RawMessage {
1683 frame,
1684 frame_addr: addr,
1685 protocol_header: phead,
1686 payload: v,
1687 };
1688
1689 msg.frame.size = msg.packed_size() as u16;
1690
1691 Ok(msg)
1692 }
1693
1694 pub fn packed_size(&self) -> usize {
1696 Frame::packed_size()
1697 + FrameAddress::packed_size()
1698 + ProtocolHeader::packed_size()
1699 + self.payload.len()
1700 }
1701
1702 pub fn validate(&self) {
1704 self.frame.validate();
1705 self.frame_addr.validate();
1706 self.protocol_header.validate();
1707 }
1708
1709 pub fn pack(&self) -> Result<Vec<u8>, Error> {
1713 let mut v = Vec::with_capacity(self.packed_size());
1714 v.extend(self.frame.pack()?);
1715 v.extend(self.frame_addr.pack()?);
1716 v.extend(self.protocol_header.pack()?);
1717 v.extend(&self.payload);
1718 Ok(v)
1719 }
1720 pub fn unpack(v: &[u8]) -> Result<RawMessage, Error> {
1723 let mut start = 0;
1724 let frame = Frame::unpack(v)?;
1725 frame.validate();
1726 start += Frame::packed_size();
1727 let addr = FrameAddress::unpack(&v[start..])?;
1728 addr.validate();
1729 start += FrameAddress::packed_size();
1730 let proto = ProtocolHeader::unpack(&v[start..])?;
1731 proto.validate();
1732 start += ProtocolHeader::packed_size();
1733
1734 let body = Vec::from(&v[start..(frame.size as usize)]);
1735
1736 Ok(RawMessage {
1737 frame,
1738 frame_addr: addr,
1739 protocol_header: proto,
1740 payload: body,
1741 })
1742 }
1743}
1744
1745#[derive(Clone, Debug, Serialize)]
1746pub struct ProductInfo {
1747 pub name: &'static str,
1748 pub identifier: &'static str,
1749 pub company: &'static str,
1750 pub vendor_id: i64,
1751 pub product_id: i64,
1752 pub capabilities: ProductCapabilities
1753}
1754
1755#[derive(Clone, Debug, Serialize)]
1756pub struct ProductCapabilities {
1757 pub has_color: bool,
1758 pub has_variable_color_temp: bool,
1759 pub has_ir: bool,
1760 pub has_hev: bool,
1761 pub has_chain: bool,
1762 pub has_matrix: bool,
1763 pub has_multizone: bool,
1764 pub min_kelvin: i64,
1765 pub max_kelvin: i64
1766}
1767
1768
1769#[rustfmt::skip]
1775pub fn get_product_info(vendor: u32, product: u32) -> Option<&'static ProductInfo> {
1776 match (vendor, product) {
1777 (1, 1) => Some(&ProductInfo { name: "LIFX Original 1000", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 1, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1778 (1, 3) => Some(&ProductInfo { name: "LIFX Color 650", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 3, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1779 (1, 10) => Some(&ProductInfo { name: "LIFX White 800 (Low Voltage)", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 10, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 6500 } }),
1780 (1, 11) => Some(&ProductInfo { name: "LIFX White 800 (High Voltage)", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 11, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 6500 } }),
1781 (1, 15) => Some(&ProductInfo { name: "LIFX Color 1000", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 15, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1782 (1, 18) => Some(&ProductInfo { name: "LIFX White 900 BR30 (Low Voltage)", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 18, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1783 (1, 19) => Some(&ProductInfo { name: "LIFX White 900 BR30 (High Voltage)", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 19, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1784 (1, 20) => Some(&ProductInfo { name: "LIFX Color 1000 BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 20, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1785 (1, 22) => Some(&ProductInfo { name: "LIFX Color 1000", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 22, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1786 (1, 27) => Some(&ProductInfo { name: "LIFX A19", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 27, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1787 (1, 28) => Some(&ProductInfo { name: "LIFX BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 28, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1788 (1, 29) => Some(&ProductInfo { name: "LIFX+ A19", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 29, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1789 (1, 30) => Some(&ProductInfo { name: "LIFX+ BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 30, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1790 (1, 31) => Some(&ProductInfo { name: "LIFX Z", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 31, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1791 (1, 32) => Some(&ProductInfo { name: "LIFX Z 2", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 32, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1792 (1, 36) => Some(&ProductInfo { name: "LIFX Downlight", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 36, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1793 (1, 37) => Some(&ProductInfo { name: "LIFX Downlight", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 37, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1794 (1, 38) => Some(&ProductInfo { name: "LIFX Beam", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 38, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1795 (1, 39) => Some(&ProductInfo { name: "LIFX Downlight White to Warm", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 39, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1796 (1, 40) => Some(&ProductInfo { name: "LIFX Downlight", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 40, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1797 (1, 43) => Some(&ProductInfo { name: "LIFX A19", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 43, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1798 (1, 44) => Some(&ProductInfo { name: "LIFX BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 44, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1799 (1, 45) => Some(&ProductInfo { name: "LIFX+ A19", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 45, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1800 (1, 46) => Some(&ProductInfo { name: "LIFX+ BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 46, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1801 (1, 49) => Some(&ProductInfo { name: "LIFX Mini", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 49, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1802 (1, 50) => Some(&ProductInfo { name: "LIFX Mini Day and Dusk", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 50, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 6500 } }),
1803 (1, 51) => Some(&ProductInfo { name: "LIFX Mini White", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 51, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 2700 } }),
1804 (1, 52) => Some(&ProductInfo { name: "LIFX GU10", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 52, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1805 (1, 53) => Some(&ProductInfo { name: "LIFX GU10", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 53, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1806 (1, 55) => Some(&ProductInfo { name: "LIFX Tile", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 55, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: true, has_matrix: true, has_multizone: false, min_kelvin: 2500, max_kelvin: 9000 } }),
1807 (1, 57) => Some(&ProductInfo { name: "LIFX Candle", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 57, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1808 (1, 59) => Some(&ProductInfo { name: "LIFX Mini Color", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 59, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1809 (1, 60) => Some(&ProductInfo { name: "LIFX Mini Day and Dusk", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 60, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 6500 } }),
1810 (1, 61) => Some(&ProductInfo { name: "LIFX Mini White", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 61, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 2700 } }),
1811 (1, 62) => Some(&ProductInfo { name: "LIFX A19", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 62, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1812 (1, 63) => Some(&ProductInfo { name: "LIFX BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 63, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1813 (1, 64) => Some(&ProductInfo { name: "LIFX A19 Night Vision", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 64, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1814 (1, 65) => Some(&ProductInfo { name: "LIFX BR30 Night Vision", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 65, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1815 (1, 66) => Some(&ProductInfo { name: "LIFX Mini White", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 66, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 2700 } }),
1816 (1, 68) => Some(&ProductInfo { name: "LIFX Candle", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 68, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: true, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1817 (1, 81) => Some(&ProductInfo { name: "LIFX Candle White to Warm", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 81, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2200, max_kelvin: 6500 } }),
1818 (1, 82) => Some(&ProductInfo { name: "LIFX Filament Clear", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 82, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2100, max_kelvin: 2100 } }),
1819 (1, 85) => Some(&ProductInfo { name: "LIFX Filament Amber", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 85, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2000, max_kelvin: 2000 } }),
1820 (1, 87) => Some(&ProductInfo { name: "LIFX Mini White", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 87, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 2700 } }),
1821 (1, 88) => Some(&ProductInfo { name: "LIFX Mini White", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 88, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2700, max_kelvin: 2700 } }),
1822 (1, 90) => Some(&ProductInfo { name: "LIFX Clean", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 90, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: true, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1823 (1, 91) => Some(&ProductInfo { name: "LIFX Color", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 91, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1824 (1, 92) => Some(&ProductInfo { name: "LIFX Color", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 92, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1825 (1, 93) => Some(&ProductInfo { name: "LIFX A19 US", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 93, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1826 (1, 94) => Some(&ProductInfo { name: "LIFX BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 94, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1827 (1, 96) => Some(&ProductInfo { name: "LIFX Candle White to Warm", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 96, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2200, max_kelvin: 6500 } }),
1828 (1, 97) => Some(&ProductInfo { name: "LIFX A19", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 97, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1829 (1, 98) => Some(&ProductInfo { name: "LIFX BR30", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 98, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1830 (1, 99) => Some(&ProductInfo { name: "LIFX Clean", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 99, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: false, has_hev: true, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1831 (1, 100) => Some(&ProductInfo { name: "LIFX Filament Clear", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 100, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2100, max_kelvin: 2100 } }),
1832 (1, 101) => Some(&ProductInfo { name: "LIFX Filament Amber", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 101, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 2000, max_kelvin: 2000 } }),
1833 (1, 109) => Some(&ProductInfo { name: "LIFX A19 Night Vision", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 109, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1834 (1, 110) => Some(&ProductInfo { name: "LIFX BR30 Night Vision", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 110, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1835 (1, 111) => Some(&ProductInfo { name: "LIFX A19 Night Vision", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 111, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1836 (1, 112) => Some(&ProductInfo { name: "LIFX BR30 Night Vision Intl", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 112, capabilities: ProductCapabilities { has_color: true, has_variable_color_temp: true, has_ir: true, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1837 (1, 113) => Some(&ProductInfo { name: "LIFX Mini WW US", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 113, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1838 (1, 114) => Some(&ProductInfo { name: "LIFX Mini WW Intl", identifier: "lifx_unknown", company: "LIFX", vendor_id: 1, product_id: 114, capabilities: ProductCapabilities { has_color: false, has_variable_color_temp: true, has_ir: false, has_hev: false, has_chain: false, has_matrix: false, has_multizone: false, min_kelvin: 1500, max_kelvin: 9000 } }),
1839 (_, _) => None
1840 }
1841}
1842
1843#[cfg(test)]
1844mod tests {
1845 use super::*;
1846
1847 #[test]
1848 fn test_frame() {
1849 let frame = Frame {
1850 size: 0x1122,
1851 origin: 0,
1852 tagged: true,
1853 addressable: true,
1854 protocol: 1024,
1855 source: 1234567,
1856 };
1857 frame.validate();
1858
1859 let v = frame.pack().unwrap();
1860 println!("{:?}", v);
1861 assert_eq!(v[0], 0x22);
1862 assert_eq!(v[1], 0x11);
1863
1864 assert_eq!(v.len(), Frame::packed_size());
1865
1866 let unpacked = Frame::unpack(&v).unwrap();
1867 assert_eq!(frame, unpacked);
1868 }
1869
1870 #[test]
1871 fn test_decode_frame() {
1872 let v = vec![0x28, 0x00, 0x00, 0x54, 0x42, 0x52, 0x4b, 0x52];
1874 let frame = Frame::unpack(&v).unwrap();
1875 println!("{:?}", frame);
1876
1877 assert_eq!(frame.size, 0x0028);
1889 assert_eq!(frame.origin, 1);
1890 assert_eq!(frame.addressable, true);
1891 assert_eq!(frame.tagged, false);
1892 assert_eq!(frame.protocol, 1024);
1893 assert_eq!(frame.source, 0x524b5242);
1894 }
1895
1896 #[test]
1897 fn test_decode_frame1() {
1898 let v = vec![0x24, 0x00, 0x00, 0x14, 0xca, 0x41, 0x37, 0x05];
1900 let frame = Frame::unpack(&v).unwrap();
1901 println!("{:?}", frame);
1902
1903 assert_eq!(frame.size, 0x0024);
1906 assert_eq!(frame.origin, 0);
1907 assert_eq!(frame.tagged, false);
1908 assert_eq!(frame.addressable, true);
1909 assert_eq!(frame.protocol, 1024);
1910 assert_eq!(frame.source, 0x053741ca);
1911 }
1912
1913 #[test]
1914 fn test_frame_address() {
1915 let frame = FrameAddress {
1916 target: 0x11224488,
1917 reserved: [0; 6],
1918 reserved2: 0,
1919 ack_required: true,
1920 res_required: false,
1921 sequence: 248,
1922 };
1923 frame.validate();
1924
1925 let v = frame.pack().unwrap();
1926 assert_eq!(v.len(), FrameAddress::packed_size());
1927 println!("Packed FrameAddress: {:?}", v);
1928
1929 let unpacked = FrameAddress::unpack(&v).unwrap();
1930 assert_eq!(frame, unpacked);
1931 }
1932
1933 #[test]
1934 fn test_decode_frame_address() {
1935 let v = vec![
1937 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1938 0x01, 0x9c,
1939 ];
1940 assert_eq!(v.len(), FrameAddress::packed_size());
1941
1942 let frame = FrameAddress::unpack(&v).unwrap();
1943 frame.validate();
1944 println!("FrameAddress: {:?}", frame);
1945 }
1946
1947 #[test]
1948 fn test_protocol_header() {
1949 let frame = ProtocolHeader {
1950 reserved: 0,
1951 reserved2: 0,
1952 typ: 0x4455,
1953 };
1954 frame.validate();
1955
1956 let v = frame.pack().unwrap();
1957 assert_eq!(v.len(), ProtocolHeader::packed_size());
1958 println!("Packed ProtocolHeader: {:?}", v);
1959
1960 let unpacked = ProtocolHeader::unpack(&v).unwrap();
1961 assert_eq!(frame, unpacked);
1962 }
1963
1964 #[test]
1965 fn test_decode_protocol_header() {
1966 let v = vec![
1968 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
1969 ];
1970 assert_eq!(v.len(), ProtocolHeader::packed_size());
1971
1972 let frame = ProtocolHeader::unpack(&v).unwrap();
1973 frame.validate();
1974 println!("ProtocolHeader: {:?}", frame);
1975 }
1976
1977 #[test]
1978 fn test_decode_full() {
1979 let v = vec![
1980 0x24, 0x00, 0x00, 0x14, 0xca, 0x41, 0x37, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1981 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00, 0x00, 0x00,
1982 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00,
1983 ];
1984
1985 let msg = RawMessage::unpack(&v).unwrap();
1986 msg.validate();
1987 println!("{:#?}", msg);
1988 }
1989
1990 #[test]
1991 fn test_decode_full_1() {
1992 let v = vec![
1993 0x58, 0x00, 0x00, 0x54, 0xca, 0x41, 0x37, 0x05, 0xd0, 0x73, 0xd5, 0x02, 0x97, 0xde,
1994 0x00, 0x00, 0x4c, 0x49, 0x46, 0x58, 0x56, 0x32, 0x00, 0xc0, 0x44, 0x30, 0xeb, 0x47,
1995 0xc4, 0x48, 0x18, 0x14, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
1996 0xb8, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x69, 0x74, 0x63, 0x68, 0x65, 0x6e, 0x00,
1997 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1998 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
1999 0x00, 0x00, 0x00, 0x00,
2000 ];
2001
2002 let msg = RawMessage::unpack(&v).unwrap();
2003 msg.validate();
2004 println!("{:#?}", msg);
2005 }
2006
2007 #[test]
2008 fn test_build_a_packet() {
2009 let msg = Message::LightSetColor {
2012 reserved: 0,
2013 color: HSBK {
2014 hue: 21845,
2015 saturation: 0xffff,
2016 brightness: 0xffff,
2017 kelvin: 3500,
2018 },
2019 duration: 1024,
2020 };
2021
2022 let raw = RawMessage::build(
2023 &BuildOptions {
2024 target: None,
2025 ack_required: false,
2026 res_required: false,
2027 sequence: 0,
2028 source: 0,
2029 },
2030 msg,
2031 )
2032 .unwrap();
2033
2034 let bytes = raw.pack().unwrap();
2035 println!("{:?}", bytes);
2036 assert_eq!(bytes.len(), 49);
2037 assert_eq!(
2038 bytes,
2039 vec![
2040 0x31, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2041 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
2042 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x00, 0x55, 0x55, 0xFF, 0xFF, 0xFF,
2043 0xFF, 0xAC, 0x0D, 0x00, 0x04, 0x00, 0x00
2044 ]
2045 );
2046 }
2047}