lifx_rs/
lan.rs

1//! This crate provides low-level message types and structures for dealing with the LIFX LAN protocol.
2//!
3//! This lets you control lights on your local area network.  More info can be found here:
4//! https://lan.developer.lifx.com/
5//!
6//! Since this is a low-level library, it does not deal with issues like talking to the network,
7//! caching light state, or waiting for replies.  This should be done at a higher-level library.
8//!
9//! # Discovery
10//!
11//! To discover lights on your LAN, send a [Message::GetService] message as a UDP broadcast to port 56700
12//! When a device is discovered, the [Service] types and IP port are provided.  To get additional
13//! info about each device, send additional Get messages directly to each device (by setting the
14//! [FrameAddress::target] field to the bulbs target ID, and then send a UDP packet to the IP address
15//! associated with the device).
16//!
17//! # Reserved fields
18//! When *constructing* packets, you must always set every reserved field to zero.  However, it's
19//! possible to receive packets with these fields set to non-zero values.  Be conservative in what
20//! you send, and liberal in what you accept.
21//!
22//! # Unknown values
23//! It's common to see packets for LIFX bulbs that don't match the documented protocol.  These are
24//! suspected to be internal messages that are used by offical LIFX apps, but that aren't documented.
25
26use 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/// Various message encoding/decoding errors
36#[derive(Error, Debug)]
37pub enum Error {
38    /// This error means we were unable to parse a raw message because its type is unknown.
39    ///
40    /// LIFX devices are known to send messages that are not officially documented, so this error
41    /// type does not necessarily represent a bug.
42    #[error("unknown message type: `{0}`")]
43    UnknownMessageType(u16),
44    /// This error means one of the message fields contains an invalid or unsupported value.
45    #[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/// Lifx strings are fixed-length (32-bytes maximum)
128#[derive(Debug, Clone, PartialEq)]
129pub struct LifxString(pub String);
130
131impl LifxString {
132    /// Constructs a new LifxString, truncating to 32 characters.
133    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//trace_macros!(true);
356//message_types! {
357//    /// GetService - 2
358//    ///
359//    /// Sent by a client to acquire responses from all devices on the local network.
360//    GetService(2, ),
361//    /// StateService - 3
362//    ///
363//    /// Response to GetService message.  Provides the device Service and Port.  If the Service
364//    /// is temporarily unavailable, then the port value will be zero
365//    StateService(3, {
366//        service: Service,
367//        port: u32
368//    })
369//}
370//trace_macros!(false);
371
372/// What services are exposed by the device.
373///
374/// LIFX only documents the UDP service, though bulbs may support other undocumented services.
375/// Since these other services are unsupported by the lifx-core library, a message with a non-UDP
376/// service cannot be constructed.
377#[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/// Controls how/when multizone devices apply color changes
391///
392/// See also [Message::SetColorZones].
393#[repr(u8)]
394#[derive(Debug, Copy, Clone)]
395pub enum ApplicationRequest {
396    /// Don't apply the requested changes until a message with Apply or ApplyOnly is sent
397    NoApply = 0,
398    /// Apply the changes immediately and apply any pending changes
399    Apply = 1,
400    /// Ignore the requested changes in this message and only apply pending changes
401    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/// Decoded LIFX Messages
415///
416/// This enum lists all of the LIFX message types known to this library.
417///
418/// Note that other message types exist, but are not officially documented (and so are not
419/// available here).
420#[derive(Clone, Debug)]
421pub enum Message {
422    /// GetService - 2
423    ///
424    /// Sent by a client to acquire responses from all devices on the local network. No payload is
425    /// required. Causes the devices to transmit a StateService message.
426    GetService,
427
428    /// StateService - 3
429    ///
430    /// Response to [Message::GetService] message.
431    StateService {
432        /// Port number of the light.  If the service is temporarily unavailable, then the port value
433        /// will be 0.
434        port: u32,
435        /// unsigned 8-bit integer, maps to `Service`
436        service: Service,
437    },
438
439    /// GetHostInfo - 12
440    ///
441    /// Get Host MCU information. No payload is required. Causes the device to transmit a
442    /// [Message::StateHostInfo] message.
443    GetHostInfo,
444
445    /// StateHostInfo - 13
446    ///
447    /// Response to [Message::GetHostInfo] message.
448    ///
449    /// Provides host MCU information.
450    StateHostInfo {
451        /// radio receive signal strength in miliWatts
452        signal: f32,
453        /// Bytes transmitted since power on
454        tx: u32,
455        /// Bytes received since power on
456        rx: u32,
457        reserved: i16,
458    },
459
460    /// GetHostFirmware - 14
461    ///
462    /// Gets Host MCU firmware information. No payload is required. Causes the device to transmit a
463    /// [Message::StateHostFirmware] message.
464    GetHostFirmware,
465
466    /// StateHostFirmware - 15
467    ///
468    /// Response to [Message::GetHostFirmware] message.
469    ///
470    /// Provides host firmware information.
471    StateHostFirmware {
472        /// Firmware build time (absolute time in nanoseconds since epoch)
473        build: u64,
474        reserved: u64,
475        /// Firmware version
476        version: u32,
477    },
478
479    /// GetWifiInfo - 16
480    ///
481    /// Get Wifi subsystem information. No payload is required. Causes the device to transmit a
482    /// [Message::StateWifiInfo] message.
483    GetWifiInfo,
484
485    /// StateWifiInfo - 17
486    ///
487    /// Response to [Message::GetWifiInfo] message.
488    ///
489    /// Provides Wifi subsystem information.
490    StateWifiInfo {
491        /// Radio receive signal strength in mw
492        signal: f32,
493        /// bytes transmitted since power on
494        tx: u32,
495        /// bytes received since power on
496        rx: u32,
497        reserved: i16,
498    },
499
500    /// GetWifiFirmware - 18
501    ///
502    /// Get Wifi subsystem firmware. No payload is required. Causes the device to transmit a
503    /// [Message::StateWifiFirmware] message.
504    GetWifiFirmware,
505
506    /// StateWifiFirmware - 19
507    /// \
508    /// Response to [Message::GetWifiFirmware] message.
509    ///
510    /// Provides Wifi subsystem information.
511    StateWifiFirmware {
512        /// firmware build time (absolute time in nanoseconds since epoch)
513        build: u64,
514        reserved: u64,
515        /// firmware version
516        version: u32,
517    },
518
519    /// GetPower - 20
520    ///
521    /// Get device power level. No payload is required. Causes the device to transmit a [Message::StatePower]
522    /// message
523    GetPower,
524
525    /// SetPower - 21
526    ///
527    /// Set device power level.
528    SetPower {
529        /// normally a u16, but only 0 and 65535 are supported.
530        ///
531        /// Zero implies standby and non-zero sets a corresponding power draw level.
532        level: PowerLevel,
533    },
534
535    /// StatePower - 22
536    ///
537    /// Response to [Message::GetPower] message.
538    ///
539    /// Provides device power level.
540    StatePower { level: PowerLevel },
541
542    /// GetLabel - 23
543    ///
544    /// Get device label. No payload is required. Causes the device to transmit a [Message::StateLabel]
545    /// message.
546    GetLabel,
547
548    /// SetLabel - 24
549    ///
550    /// Set the device label text.
551    SetLabel { label: LifxString },
552
553    /// StateLabel - 25
554    ///
555    /// Response to [Message::GetLabel] message.
556    ///
557    /// Provides device label.
558    StateLabel { label: LifxString },
559
560    /// GetVersion - 32
561    ///
562    /// Get the hardware version. No payload is required. Causes the device to transmit a
563    /// [Message::StateVersion] message.
564    GetVersion,
565
566    /// StateVersion - 33
567    ///
568    /// Response to [Message::GetVersion] message.
569    ///
570    /// Provides the hardware version of the device.
571    StateVersion {
572        /// vendor ID
573        vendor: u32,
574        /// product ID
575        product: u32,
576        /// hardware version
577        version: u32,
578    },
579
580    /// GetInfo - 34
581    ///
582    /// Get run-time information. No payload is required. Causes the device to transmit a [Message::StateInfo]
583    /// message.
584    GetInfo,
585
586    /// StateInfo - 35
587    ///
588    /// Response to [Message::GetInfo] message.
589    ///
590    /// Provides run-time information of device.
591    StateInfo {
592        /// current time (absolute time in nanoseconds since epoch)
593        time: u64,
594        /// time since last power on (relative time in nanoseconds)
595        uptime: u64,
596        /// last power off period (5 second accuracy, in nanoseconds)
597        downtime: u64,
598    },
599
600    /// Acknowledgement - 45
601    ///
602    /// Response to any message sent with ack_required set to 1. See message header frame address.
603    ///
604    /// (Note that technically this message has no payload, but the frame sequence number is stored
605    /// here for convenience).
606    Acknowledgement { seq: u8 },
607
608    /// GetLocation - 48
609    ///
610    /// Ask the bulb to return its location information. No payload is required. Causes the device
611    /// to transmit a [Message::StateLocation] message.
612    GetLocation,
613
614    /// SetLocation -- 49
615    ///
616    /// Set the device location
617    SetLocation {
618        /// GUID byte array
619        location: LifxIdent,
620        /// text label for location
621        label: LifxString,
622        /// UTC timestamp of last label update in nanoseconds
623        updated_at: u64,
624    },
625
626    /// StateLocation - 50
627    ///
628    /// Device location.
629    StateLocation {
630        location: LifxIdent,
631        label: LifxString,
632        updated_at: u64,
633    },
634
635    /// GetGroup - 51
636    ///
637    /// Ask the bulb to return its group membership information.
638    /// No payload is required.
639    /// Causes the device to transmit a [Message::StateGroup] message.
640    GetGroup,
641
642    /// SetGroup - 52
643    ///
644    /// Set the device group
645    SetGroup {
646        group: LifxIdent,
647        label: LifxString,
648        updated_at: u64,
649    },
650
651    /// StateGroup - 53
652    ///
653    /// Device group.
654    StateGroup {
655        group: LifxIdent,
656        label: LifxString,
657        updated_at: u64,
658    },
659
660    /// EchoRequest - 58
661    ///
662    /// Request an arbitrary payload be echoed back. Causes the device to transmit an [Message::EchoResponse]
663    /// message.
664    EchoRequest { payload: EchoPayload },
665
666    /// EchoResponse - 59
667    ///
668    /// Response to [Message::EchoRequest] message.
669    ///
670    /// Echo response with payload sent in the EchoRequest.
671    ///
672    EchoResponse { payload: EchoPayload },
673
674    /// Get - 101
675    ///
676    /// Sent by a client to obtain the light state. No payload required. Causes the device to
677    /// transmit a [Message::LightState] message.
678    LightGet,
679
680    /// SetColor - 102
681    ///
682    /// Sent by a client to change the light state.
683    ///
684    /// If the Frame Address res_required field is set to one (1) then the device will transmit a
685    /// State message.
686    LightSetColor {
687        reserved: u8,
688        /// Color in HSBK
689        color: HSBK,
690        /// Color transition time in milliseconds
691        duration: u32,
692    },
693
694    /// SetWaveform - 103
695    ///
696    /// Apply an effect to the bulb.
697    SetWaveform {
698        reserved: u8,
699        transient: bool,
700        color: HSBK,
701        /// Duration of a cycle in milliseconds
702        period: u32,
703        /// Number of cycles
704        cycles: f32,
705        /// Waveform Skew, [-32768, 32767] scaled to [0, 1].
706        skew_ratio: i16,
707        /// Waveform to use for transition.
708        waveform: Waveform,
709    },
710
711    /// State - 107
712    ///
713    /// Sent by a device to provide the current light state.
714    LightState {
715        color: HSBK,
716        reserved: i16,
717        power: PowerLevel,
718        label: LifxString,
719        reserved2: u64,
720    },
721
722    /// GetPower - 116
723    ///
724    /// Sent by a client to obtain the power level. No payload required. Causes the device to
725    /// transmit a StatePower message.
726    LightGetPower,
727
728    /// SetPower - 117
729    ///
730    /// Sent by a client to change the light power level.
731    ///
732    /// Field   Type
733    /// level   unsigned 16-bit integer
734    /// duration    unsigned 32-bit integer
735    /// The power level must be either 0 or 65535.
736    ///
737    /// The duration is the power level transition time in milliseconds.
738    ///
739    /// If the Frame Address res_required field is set to one (1) then the device will transmit a
740    /// StatePower message.
741    LightSetPower { level: u16, duration: u32 },
742
743    /// StatePower - 118
744    ///
745    /// Sent by a device to provide the current power level.
746    ///
747    /// Field   Type
748    /// level   unsigned 16-bit integer
749    LightStatePower { level: u16 },
750
751    /// SetWaveformOptional - 119
752    ///
753    /// Apply an effect to the bulb.
754    SetWaveformOptional {
755        reserved: u8,
756        transient: bool,
757        color: HSBK,
758        /// Duration of a cycle in milliseconds
759        period: u32,
760        /// Number of cycles
761        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    /// GetInfrared - 120
772    ///
773    /// Gets the current maximum power level of the Infraed channel
774    LightGetInfrared,
775
776    /// StateInfrared - 121
777    ///
778    /// Indicates the current maximum setting for the infrared channel.
779    LightStateInfrared { brightness: u16 },
780
781    /// SetInfrared -- 122
782    ///
783    /// Set the current maximum brightness for the infrared channel.
784    LightSetInfrared { brightness: u16 },
785
786    /// SetColorZones - 501
787    ///
788    /// This message is used for changing the color of either a single or multiple zones.
789    /// The changes are stored in a buffer and are only applied once a message with either
790    /// [ApplicationRequest::Apply] or [ApplicationRequest::ApplyOnly] set.
791    SetColorZones {
792        start_index: u8,
793        end_index: u8,
794        color: HSBK,
795        duration: u32,
796        apply: ApplicationRequest,
797    },
798
799    /// GetColorZones - 502
800    ///
801    /// GetColorZones is used to request the zone colors for a range of zones. The bulb will respond
802    /// with either [Message::StateZone] or [Message::StateMultiZone] messages as required to cover
803    /// the requested range. The bulb may send state messages that cover more than the requested
804    /// zones. Any zones outside the requested indexes will still contain valid values at the time
805    /// the message was sent.
806    GetColorZones { start_index: u8, end_index: u8 },
807
808    /// StateZone - 503
809
810    /// The StateZone message represents the state of a single zone with the `index` field indicating
811    /// which zone is represented. The `count` field contains the count of the total number of zones
812    /// available on the device.
813    StateZone { count: u8, index: u8, color: HSBK },
814
815    /// StateMultiZone - 506
816    ///
817    /// The StateMultiZone message represents the state of eight consecutive zones in a single message.
818    /// As in the StateZone message the `count` field represents the count of the total number of
819    /// zones available on the device. In this message the `index` field represents the index of
820    /// `color0` and the rest of the colors are the consecutive zones thus the index of the
821    /// `color_n` zone will be `index + n`.
822    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    /// Tries to parse the payload in a [RawMessage], based on its message type.
887    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/// Bulb color (Hue-Saturation-Brightness-Kelvin)
1022///
1023/// # Notes:
1024///
1025/// Colors are represented as Hue-Saturation-Brightness-Kelvin, or HSBK
1026///
1027/// When a light is displaying whites, saturation will be zero, hue will be ignored, and only
1028/// brightness and kelvin will matter.
1029///
1030/// Normal values for "kelvin" are from 2500 (warm/yellow) to 9000 (cool/blue)
1031///
1032/// When a light is displaying colors, kelvin is ignored.
1033///
1034/// To display "pure" colors, set saturation to full (65535).
1035#[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
1067/// Describe (in english words) the color temperature as given in kelvin.
1068///
1069/// These descriptions match the values shown in the LIFX mobile app.
1070pub 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/// The raw message structure
1109///
1110/// Contains a low-level protocol info.  This is what is sent and received via UDP packets.
1111///
1112/// To parse the payload, use [Message::from_raw].
1113#[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/// The Frame section contains information about the following:
1122///
1123/// * Size of the entire message
1124/// * LIFX Protocol number: must be 1024 (decimal)
1125/// * Use of the Frame Address target field
1126/// * Source identifier
1127///
1128/// The `tagged` field is a boolean that indicates whether the Frame Address target field is
1129/// being used to address an individual device or all devices.  If `tagged` is true, then the
1130/// `target` field should be all zeros.
1131#[derive(Debug, Clone, Copy, PartialEq)]
1132pub struct Frame {
1133    /// 16 bits: Size of entire message in bytes including this field
1134    pub size: u16,
1135
1136    /// 2 bits: Message origin indicator: must be zero (0)
1137    pub origin: u8,
1138
1139    /// 1 bit: Determines usage of the Frame Address target field
1140    pub tagged: bool,
1141
1142    /// 1 bit: Message includes a target address: must be one (1)
1143    pub addressable: bool,
1144
1145    /// 12 bits: Protocol number: must be 1024 (decimal)
1146    pub protocol: u16,
1147
1148    /// 32 bits: Source identifier: unique value set by the client, used by responses.
1149    ///
1150    /// If the source identifier is zero, then the LIFX device may send a broadcast message that can
1151    /// be received by all clients on the same subnet.
1152    ///
1153    /// If this packet is a reply, then this source field will be set to the same value as the client-
1154    /// sent request packet.
1155    pub source: u32,
1156}
1157
1158/// The Frame Address section contains the following routing information:
1159///
1160/// * Target device address
1161/// * Acknowledgement message is required flag
1162/// * State response message is required flag
1163/// * Message sequence number
1164#[derive(Debug, Clone, Copy, PartialEq)]
1165pub struct FrameAddress {
1166    /// 64 bits: 6 byte device address (MAC address) or zero (0) means all devices
1167    pub target: u64,
1168
1169    /// 48 bits: Must all be zero (0)
1170    pub reserved: [u8; 6],
1171
1172    /// 6 bits: Reserved
1173    pub reserved2: u8,
1174
1175    /// 1 bit: Acknowledgement message required
1176    pub ack_required: bool,
1177
1178    /// 1 bit: Response message required
1179    pub res_required: bool,
1180
1181    /// 8 bits: Wrap around message sequence number
1182    pub sequence: u8,
1183}
1184
1185#[derive(Debug, Clone, Copy, PartialEq)]
1186pub struct ProtocolHeader {
1187    /// 64 bits: Reserved
1188    pub reserved: u64,
1189
1190    /// 16 bits: Message type determines the payload being used
1191    pub typ: u16,
1192
1193    /// 16 bits: Reserved
1194    pub reserved2: u16,
1195}
1196
1197impl Frame {
1198    /// packed sized, in bytes
1199    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        // pack origin + tagged + addressable +  protocol as a u16
1214        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        // origin + tagged + addressable + protocol
1231        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        //assert_eq!(self.reserved, [0;6]);
1265        //assert_eq!(self.reserved2, 0);
1266    }
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        //assert_eq!(self.reserved, 0);
1318        //assert_eq!(self.reserved2, 0);
1319    }
1320
1321    /// Packs this part of the packet into some bytes
1322    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/// Options used to contruct a [RawMessage].
1347///
1348/// See also [RawMessage::build].
1349#[derive(Debug, Clone)]
1350pub struct BuildOptions {
1351    /// If not `None`, this is the ID of the device you want to address.
1352    ///
1353    /// To look up the ID of a device, extract it from the [FrameAddress::target] field when a
1354    /// device sends a [Message::StateService] message.
1355    pub target: Option<u64>,
1356    /// Acknowledgement message required.
1357    ///
1358    /// Causes the light to send an [Message::Acknowledgement] message.
1359    pub ack_required: bool,
1360    /// Response message required.
1361    ///
1362    /// Some message types are sent by clients to get data from a light.  These should always have
1363    /// `res_required` set to true.
1364    pub res_required: bool,
1365    /// A wrap around sequence number.  Optional (can be zero).
1366    ///
1367    /// By providing a unique sequence value, the response message will also contain the same
1368    /// sequence number, allowing a client to distinguish between different messages sent with the
1369    /// same `source` identifier.
1370    pub sequence: u8,
1371    /// A unique client identifier. Optional (can be zero).
1372    ///
1373    /// If the source is non-zero, then the LIFX device with send a unicast message to the IP
1374    /// address/port of the client that sent the originating message.  If zero, then the LIFX
1375    /// device may send a broadcast message that can be received by all clients on the same sub-net.
1376    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    /// Build a RawMessage (which is suitable for sending on the network) from a given Message
1393    /// type.
1394    ///
1395    /// If [BuildOptions::target] is None, then the message is addressed to all devices.  Else it should be a
1396    /// bulb UID (MAC address)
1397    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                // these types have no payload
1438            }
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    /// The total size (in bytes) of the packed version of this message.
1695    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    /// Validates that this object was constructed correctly.  Panics if not.
1703    pub fn validate(&self) {
1704        self.frame.validate();
1705        self.frame_addr.validate();
1706        self.protocol_header.validate();
1707    }
1708
1709    /// Packs this RawMessage into some bytes that can be send over the network.
1710    ///
1711    /// The length of the returned data will be [RawMessage::packed_size] in size.
1712    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    /// Given some bytes (generally read from a network socket), unpack the data into a
1721    /// `RawMessage` structure.
1722    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/// Look up info about what a LIFX product supports.
1770///
1771/// You can get the vendor and product IDs from a bulb by receiving a [Message::StateVersion] message
1772///
1773/// Data is taken from https://github.com/LIFX/products/blob/master/products.json
1774#[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        //             00    01    02    03    04    05    06    07
1873        let v = vec![0x28, 0x00, 0x00, 0x54, 0x42, 0x52, 0x4b, 0x52];
1874        let frame = Frame::unpack(&v).unwrap();
1875        println!("{:?}", frame);
1876
1877        // manual decoding:
1878        // size: 0x0028 ==> 40
1879        // 0x00, 0x54 (origin, tagged, addressable, protocol)
1880
1881        //  /-Origin ==> 0
1882        // || /- addressable=1
1883        // || |
1884        // 01010100 00000000
1885        //   |
1886        //   \- Tagged=0
1887
1888        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        //             00    01    02    03    04    05    06    07
1899        let v = vec![0x24, 0x00, 0x00, 0x14, 0xca, 0x41, 0x37, 0x05];
1900        let frame = Frame::unpack(&v).unwrap();
1901        println!("{:?}", frame);
1902
1903        // 00010100 00000000
1904
1905        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        //   1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16
1936        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        //   1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16
1967        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        // packet taken from https://lan.developer.lifx.com/docs/building-a-lifx-packet
2010
2011        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}