From 5dd72923d4c4f739db0951c959147ba1aad86278 Mon Sep 17 00:00:00 2001 From: Laszlo Deak Date: Thu, 21 Oct 2021 23:47:08 +0200 Subject: [PATCH] Switching Source/Identifier fields with Sequence Source/Identifier is a fixed, random generated value. Sequence is autoincrementing --- src/LifxNet/LifxClient.DeviceOperations.cs | 10 +- src/LifxNet/LifxClient.Discovery.cs | 331 ++++++++------- src/LifxNet/LifxClient.LightOperations.cs | 12 +- src/LifxNet/LifxClient.cs | 458 +++++++++++---------- 4 files changed, 407 insertions(+), 404 deletions(-) diff --git a/src/LifxNet/LifxClient.DeviceOperations.cs b/src/LifxNet/LifxClient.DeviceOperations.cs index 4fe5b0b..f210975 100644 --- a/src/LifxNet/LifxClient.DeviceOperations.cs +++ b/src/LifxNet/LifxClient.DeviceOperations.cs @@ -34,7 +34,7 @@ public async Task SetDevicePowerStateAsync(Device device, bool isOn) System.Diagnostics.Debug.WriteLine($"Sending DeviceSetPower({isOn}) to {device.HostName}"); FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; @@ -54,7 +54,7 @@ public async Task SetDevicePowerStateAsync(Device device, bool isOn) FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = false }; var resp = await BroadcastMessageAsync(device.HostName, header, MessageType.DeviceGetLabel).ConfigureAwait(false); @@ -74,7 +74,7 @@ public async Task SetDeviceLabelAsync(Device device, string label) FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; _ = await BroadcastMessageAsync( @@ -91,7 +91,7 @@ public Task GetDeviceVersionAsync(Device device) FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = false }; return BroadcastMessageAsync(device.HostName, header, MessageType.DeviceGetVersion); @@ -108,7 +108,7 @@ public Task GetDeviceHostFirmwareAsync(Device device) FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = false }; return BroadcastMessageAsync(device.HostName, header, MessageType.DeviceGetHostFirmware); diff --git a/src/LifxNet/LifxClient.Discovery.cs b/src/LifxNet/LifxClient.Discovery.cs index 3f5b402..695e379 100644 --- a/src/LifxNet/LifxClient.Discovery.cs +++ b/src/LifxNet/LifxClient.Discovery.cs @@ -1,174 +1,171 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; namespace LifxNet { - public partial class LifxClient : IDisposable - { - private static uint identifier = 1; - private static object identifierLock = new object(); - private UInt32 discoverSourceID; - private CancellationTokenSource? _DiscoverCancellationSource; - private Dictionary DiscoveredBulbs = new Dictionary(); - - private static uint GetNextIdentifier() - { - lock (identifierLock) - return identifier++; - } - - /// - /// Event fired when a LIFX bulb is discovered on the network - /// - public event EventHandler? DeviceDiscovered; - /// - /// Event fired when a LIFX bulb hasn't been seen on the network for a while (for more than 5 minutes) - /// - public event EventHandler? DeviceLost; - - private IList devices = new List(); - - /// - /// Gets a list of currently known devices - /// - public IEnumerable Devices { get { return devices; } } - - /// - /// Event args for and events. - /// - public sealed class DeviceDiscoveryEventArgs : EventArgs - { - internal DeviceDiscoveryEventArgs(Device device) => Device = device; - /// - /// The device the event relates to - /// - public Device Device { get; } - } - - private void ProcessDeviceDiscoveryMessage(System.Net.IPAddress remoteAddress, int remotePort, LifxResponse msg) - { + public partial class LifxClient : IDisposable + { + private static int sequence = 1; + private CancellationTokenSource? _DiscoverCancellationSource; + private Dictionary DiscoveredBulbs = new Dictionary(); + + private static byte GetNextSequence() + { + unchecked + { + return (byte)Interlocked.Increment(ref sequence); + } + } + + /// + /// Event fired when a LIFX bulb is discovered on the network + /// + public event EventHandler? DeviceDiscovered; + /// + /// Event fired when a LIFX bulb hasn't been seen on the network for a while (for more than 5 minutes) + /// + public event EventHandler? DeviceLost; + + private IList devices = new List(); + + /// + /// Gets a list of currently known devices + /// + public IEnumerable Devices { get { return devices; } } + + /// + /// Event args for and events. + /// + public sealed class DeviceDiscoveryEventArgs : EventArgs + { + internal DeviceDiscoveryEventArgs(Device device) => Device = device; + /// + /// The device the event relates to + /// + public Device Device { get; } + } + + private void ProcessDeviceDiscoveryMessage(System.Net.IPAddress remoteAddress, int remotePort, LifxResponse msg) + { string id = msg.Header.TargetMacAddressName; //remoteAddress.ToString() if (DiscoveredBulbs.ContainsKey(id)) //already discovered { - DiscoveredBulbs[id].LastSeen = DateTime.UtcNow; //Update datestamp + DiscoveredBulbs[id].LastSeen = DateTime.UtcNow; //Update datestamp DiscoveredBulbs[id].HostName = remoteAddress.ToString(); //Update hostname in case IP changed return; - } - if (msg.Source != discoverSourceID || //did we request the discovery? - _DiscoverCancellationSource == null || - _DiscoverCancellationSource.IsCancellationRequested) //did we cancel discovery? - return; - - var device = new LightBulb(remoteAddress.ToString(), msg.Header.TargetMacAddress, msg.Payload[0] - , BitConverter.ToUInt32(msg.Payload, 1)) - { - LastSeen = DateTime.UtcNow - }; - DiscoveredBulbs[id] = device; - devices.Add(device); - if (DeviceDiscovered != null) - { - DeviceDiscovered(this, new DeviceDiscoveryEventArgs(device)); - } - } - - /// - /// Begins searching for bulbs. - /// - /// - /// - /// - public void StartDeviceDiscovery() - { - if (_DiscoverCancellationSource != null && !_DiscoverCancellationSource.IsCancellationRequested) - return; - _DiscoverCancellationSource = new CancellationTokenSource(); - var token = _DiscoverCancellationSource.Token; - var source = discoverSourceID = GetNextIdentifier(); - //Start discovery thread + } + if (msg.Source != ClientSource || //did we request the discovery? + _DiscoverCancellationSource == null || + _DiscoverCancellationSource.IsCancellationRequested) //did we cancel discovery? + return; + + var device = new LightBulb(remoteAddress.ToString(), msg.Header.TargetMacAddress, msg.Payload[0] + , BitConverter.ToUInt32(msg.Payload, 1)) + { + LastSeen = DateTime.UtcNow + }; + DiscoveredBulbs[id] = device; + devices.Add(device); + if (DeviceDiscovered != null) + { + DeviceDiscovered(this, new DeviceDiscoveryEventArgs(device)); + } + } + + /// + /// Begins searching for bulbs. + /// + /// + /// + /// + public void StartDeviceDiscovery() + { + if (_DiscoverCancellationSource != null && !_DiscoverCancellationSource.IsCancellationRequested) + return; + _DiscoverCancellationSource = new CancellationTokenSource(); + var token = _DiscoverCancellationSource.Token; + //Start discovery thread Task.Run(async () => - { - System.Diagnostics.Debug.WriteLine("Sending GetServices"); - FrameHeader header = new FrameHeader() - { - Identifier = source - }; - while (!token.IsCancellationRequested) - { - try - { - await BroadcastMessageAsync(null, header, MessageType.DeviceGetService); - } - catch { } - await Task.Delay(5000); - var lostDevices = devices.Where(d => (DateTime.UtcNow - d.LastSeen).TotalMinutes > 5).ToArray(); - if(lostDevices.Any()) - { - foreach(var device in lostDevices) - { - devices.Remove(device); - DiscoveredBulbs.Remove(device.MacAddressName); - if (DeviceLost != null) - DeviceLost(this, new DeviceDiscoveryEventArgs(device)); - } - } - } - }); - } - - /// - /// Stops device discovery - /// - /// - public void StopDeviceDiscovery() - { - if (_DiscoverCancellationSource == null || _DiscoverCancellationSource.IsCancellationRequested) - return; - _DiscoverCancellationSource.Cancel(); - _DiscoverCancellationSource = null; - } - } - - /// - /// LIFX Generic Device - /// - public abstract class Device - { - internal Device(string hostname, byte[] macAddress, byte service, UInt32 port) - { - if (hostname == null) - throw new ArgumentNullException(nameof(hostname)); - if(string.IsNullOrWhiteSpace(hostname)) - throw new ArgumentException(nameof(hostname)); - HostName = hostname; - MacAddress = macAddress; - Service = service; - Port = port; - LastSeen = DateTime.MinValue; - } - - /// - /// Hostname for the device - /// - public string HostName { get; internal set; } - - /// - /// Service ID - /// - public byte Service { get; } - - /// - /// Service port - /// - public UInt32 Port { get; } - - internal DateTime LastSeen { get; set; } + { + System.Diagnostics.Debug.WriteLine("Sending GetServices"); + while (!token.IsCancellationRequested) + { + try + { + FrameHeader header = new FrameHeader() + { + Sequence = GetNextSequence() + }; + await BroadcastMessageAsync(null, header, MessageType.DeviceGetService); + } + catch { } + await Task.Delay(5000); + var lostDevices = devices.Where(d => (DateTime.UtcNow - d.LastSeen).TotalMinutes > 5).ToArray(); + if (lostDevices.Any()) + { + foreach (var device in lostDevices) + { + devices.Remove(device); + DiscoveredBulbs.Remove(device.MacAddressName); + if (DeviceLost != null) + DeviceLost(this, new DeviceDiscoveryEventArgs(device)); + } + } + } + }); + } + + /// + /// Stops device discovery + /// + /// + public void StopDeviceDiscovery() + { + if (_DiscoverCancellationSource == null || _DiscoverCancellationSource.IsCancellationRequested) + return; + _DiscoverCancellationSource.Cancel(); + _DiscoverCancellationSource = null; + } + } + + /// + /// LIFX Generic Device + /// + public abstract class Device + { + internal Device(string hostname, byte[] macAddress, byte service, UInt32 port) + { + if (hostname == null) + throw new ArgumentNullException(nameof(hostname)); + if (string.IsNullOrWhiteSpace(hostname)) + throw new ArgumentException(nameof(hostname)); + HostName = hostname; + MacAddress = macAddress; + Service = service; + Port = port; + LastSeen = DateTime.MinValue; + } + + /// + /// Hostname for the device + /// + public string HostName { get; internal set; } + + /// + /// Service ID + /// + public byte Service { get; } + + /// + /// Service port + /// + public UInt32 Port { get; } + + internal DateTime LastSeen { get; set; } /// /// Gets the MAC address @@ -191,16 +188,16 @@ public string MacAddressName /// LIFX light bulb /// public sealed class LightBulb : Device - { - /// - /// Initializes a new instance of a bulb instead of relying on discovery. At least the host name must be provide for the device to be usable. - /// - /// Required - /// - /// - /// - public LightBulb(string hostname, byte[] macAddress, byte service = 0, UInt32 port = 0) : base(hostname, macAddress, service, port) - { - } + { + /// + /// Initializes a new instance of a bulb instead of relying on discovery. At least the host name must be provide for the device to be usable. + /// + /// Required + /// + /// + /// + public LightBulb(string hostname, byte[] macAddress, byte service = 0, UInt32 port = 0) : base(hostname, macAddress, service, port) + { + } } } diff --git a/src/LifxNet/LifxClient.LightOperations.cs b/src/LifxNet/LifxClient.LightOperations.cs index 022895e..b82d7cf 100644 --- a/src/LifxNet/LifxClient.LightOperations.cs +++ b/src/LifxNet/LifxClient.LightOperations.cs @@ -59,7 +59,7 @@ public async Task SetLightPowerAsync(LightBulb bulb, TimeSpan transitionDuration FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; @@ -84,7 +84,7 @@ public async Task GetLightPowerAsync(LightBulb bulb) FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; return (await BroadcastMessageAsync( @@ -146,7 +146,7 @@ public async Task SetColorAsync(LightBulb bulb, System.Diagnostics.Debug.WriteLine("Setting color to {0}", bulb.HostName); FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; UInt32 duration = (UInt32)transitionDuration.TotalMilliseconds; @@ -197,7 +197,7 @@ public Task GetLightStateAsync(LightBulb bulb) throw new ArgumentNullException(nameof(bulb)); FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = false }; return BroadcastMessageAsync( @@ -217,7 +217,7 @@ public async Task GetInfraredAsync(LightBulb bulb) FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; return (await BroadcastMessageAsync( @@ -237,7 +237,7 @@ public async Task SetInfraredAsync(Device device, UInt16 brightness) System.Diagnostics.Debug.WriteLine($"Sending SetInfrared({brightness}) to {device.HostName}"); FrameHeader header = new FrameHeader() { - Identifier = GetNextIdentifier(), + Sequence = GetNextSequence(), AcknowledgeRequired = true }; diff --git a/src/LifxNet/LifxClient.cs b/src/LifxNet/LifxClient.cs index 6ed088e..5945643 100644 --- a/src/LifxNet/LifxClient.cs +++ b/src/LifxNet/LifxClient.cs @@ -9,41 +9,47 @@ namespace LifxNet { - /// - /// LIFX Client for communicating with bulbs - /// - public partial class LifxClient : IDisposable - { - - private const int Port = 56700; - private UdpClient? _socket; + /// + /// LIFX Client for communicating with bulbs + /// + public partial class LifxClient : IDisposable + { + + private const int Port = 56700; + private UdpClient? _socket; private bool _isRunning; - private LifxClient() - { - } + internal uint ClientSource { get; } - /// - /// Creates a new LIFX client. - /// - /// client - public static Task CreateAsync() - { - LifxClient client = new LifxClient(); - client.Initialize(); + private LifxClient() + { + var source = new byte[4]; + while (BitConverter.ToUInt32(source, 0) < 1) + new Random().NextBytes(source); + ClientSource = BitConverter.ToUInt32(source, 0); + } + + /// + /// Creates a new LIFX client. + /// + /// client + public static Task CreateAsync() + { + LifxClient client = new LifxClient(); + client.Initialize(); return Task.FromResult(client); - } + } private void Initialize() - { + { IPEndPoint end = new IPEndPoint(IPAddress.Any, Port); - _socket = new UdpClient(end); + _socket = new UdpClient(end); _socket.Client.Blocking = false; - _socket.DontFragment = true; + _socket.DontFragment = true; _socket.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); _isRunning = true; StartReceiveLoop(); - } + } private void StartReceiveLoop() { @@ -62,94 +68,94 @@ private void StartReceiveLoop() }); } - private void HandleIncomingMessages(byte[] data, System.Net.IPEndPoint endpoint) - { - var remote = endpoint; - var msg = ParseMessage(data); - if (msg.Type == MessageType.DeviceStateService) - { - ProcessDeviceDiscoveryMessage(remote.Address, remote.Port, msg); - } - else - { - if (taskCompletions.ContainsKey(msg.Source)) - { - var tcs = taskCompletions[msg.Source]; - tcs(msg); - } - else - { - //TODO - } - } - System.Diagnostics.Debug.WriteLine("Received from {0}:{1}", remote.ToString(), - string.Join(",", (from a in data select a.ToString("X2")).ToArray())); + private void HandleIncomingMessages(byte[] data, System.Net.IPEndPoint endpoint) + { + var remote = endpoint; + var msg = ParseMessage(data); + if (msg.Type == MessageType.DeviceStateService) + { + ProcessDeviceDiscoveryMessage(remote.Address, remote.Port, msg); + } + else + { + if (taskCompletions.ContainsKey(msg.Header.Sequence)) + { + var tcs = taskCompletions[msg.Header.Sequence]; + tcs(msg); + } + else + { + //TODO + } + } + System.Diagnostics.Debug.WriteLine("Received from {0}:{1}", remote.ToString(), + string.Join(",", (from a in data select a.ToString("X2")).ToArray())); - } + } - /// - /// Disposes the client - /// - public void Dispose() - { + /// + /// Disposes the client + /// + public void Dispose() + { _isRunning = false; - _socket?.Dispose(); - } + _socket?.Dispose(); + } - private Task BroadcastMessageAsync(string? hostName, FrameHeader header, MessageType type, params object[] args) - where T : LifxResponse + private Task BroadcastMessageAsync(string? hostName, FrameHeader header, MessageType type, params object[] args) + where T : LifxResponse - { - List payload = new List(); - if (args != null) - { - foreach (var arg in args) - { - if (arg is UInt16) - payload.AddRange(BitConverter.GetBytes((UInt16)arg)); - else if (arg is UInt32) - payload.AddRange(BitConverter.GetBytes((UInt32)arg)); - else if (arg is byte) - payload.Add((byte)arg); - else if (arg is byte[]) - payload.AddRange((byte[])arg); - else if (arg is string) - payload.AddRange(Encoding.UTF8.GetBytes(((string)arg).PadRight(32).Take(32).ToArray())); //All strings are 32 bytes - else - throw new NotSupportedException(args.GetType().FullName); - } - } - return BroadcastMessagePayloadAsync(hostName, header, type, payload.ToArray()); - } - private async Task BroadcastMessagePayloadAsync(string? hostName, FrameHeader header, MessageType type, byte[] payload) - where T : LifxResponse - { - if (_socket == null) - throw new InvalidOperationException("No valid socket"); + { + List payload = new List(); + if (args != null) + { + foreach (var arg in args) + { + if (arg is UInt16) + payload.AddRange(BitConverter.GetBytes((UInt16)arg)); + else if (arg is UInt32) + payload.AddRange(BitConverter.GetBytes((UInt32)arg)); + else if (arg is byte) + payload.Add((byte)arg); + else if (arg is byte[]) + payload.AddRange((byte[])arg); + else if (arg is string) + payload.AddRange(Encoding.UTF8.GetBytes(((string)arg).PadRight(32).Take(32).ToArray())); //All strings are 32 bytes + else + throw new NotSupportedException(args.GetType().FullName); + } + } + return BroadcastMessagePayloadAsync(hostName, header, type, payload.ToArray()); + } + private async Task BroadcastMessagePayloadAsync(string? hostName, FrameHeader header, MessageType type, byte[] payload) + where T : LifxResponse + { + if (_socket == null) + throw new InvalidOperationException("No valid socket"); #if DEBUG - // MemoryStream ms = new MemoryStream(); - // await WritePacketToStreamAsync(ms.AsOutputStream(), header, (UInt16)type, payload).ConfigureAwait(false); - // var data = ms.ToArray(); - // System.Diagnostics.Debug.WriteLine( - // string.Join(",", (from a in data select a.ToString("X2")).ToArray())); + // MemoryStream ms = new MemoryStream(); + // await WritePacketToStreamAsync(ms.AsOutputStream(), header, (UInt16)type, payload).ConfigureAwait(false); + // var data = ms.ToArray(); + // System.Diagnostics.Debug.WriteLine( + // string.Join(",", (from a in data select a.ToString("X2")).ToArray())); #endif - if (hostName == null) - { - hostName = "255.255.255.255"; - } - TaskCompletionSource? tcs = null; + if (hostName == null) + { + hostName = "255.255.255.255"; + } + TaskCompletionSource? tcs = null; if (//header.AcknowledgeRequired && - header.Identifier > 0 && - typeof(T) != typeof(UnknownResponse)) - { - tcs = new TaskCompletionSource(); - Action action = (r) => - { - if (r.GetType() == typeof(T)) - tcs.TrySetResult((T)r); - }; - taskCompletions[header.Identifier] = action; - } + header.Sequence > 0 && + typeof(T) != typeof(UnknownResponse)) + { + tcs = new TaskCompletionSource(); + Action action = (r) => + { + if (r.GetType() == typeof(T)) + tcs.TrySetResult((T)r); + }; + taskCompletions[header.Sequence] = action; + } using (MemoryStream stream = new MemoryStream()) { @@ -157,136 +163,136 @@ private async Task BroadcastMessagePayloadAsync(string? hostName, FrameHea var msg = stream.ToArray(); await _socket.SendAsync(msg, msg.Length, hostName, Port); } - //{ - // await WritePacketToStreamAsync(stream, header, (UInt16)type, payload).ConfigureAwait(false); - //} - T result = default(T); - if(tcs != null) - { - var _ = Task.Delay(1000).ContinueWith((t) => - { - if (!t.IsCompleted) - tcs.TrySetException(new TimeoutException()); - }); - try { - result = await tcs.Task.ConfigureAwait(false); - } - finally - { - taskCompletions.Remove(header.Identifier); - } - } - return result; - } + //{ + // await WritePacketToStreamAsync(stream, header, (UInt16)type, payload).ConfigureAwait(false); + //} + T result = default(T); + if (tcs != null) + { + var _ = Task.Delay(1000).ContinueWith((t) => + { + if (!t.IsCompleted) + tcs.TrySetException(new TimeoutException()); + }); + try + { + result = await tcs.Task.ConfigureAwait(false); + } + finally + { + taskCompletions.Remove(header.Sequence); + } + } + return result; + } - private LifxResponse ParseMessage(byte[] packet) - { - using (MemoryStream ms = new MemoryStream(packet)) - { - var header = new FrameHeader(); - BinaryReader br = new BinaryReader(ms); - //frame - var size = br.ReadUInt16(); - if (packet.Length != size || size < 36) - throw new Exception("Invalid packet"); - var a = br.ReadUInt16(); //origin:2, reserved:1, addressable:1, protocol:12 - var source = br.ReadUInt32(); - //frame address - byte[] target = br.ReadBytes(8); + private LifxResponse ParseMessage(byte[] packet) + { + using (MemoryStream ms = new MemoryStream(packet)) + { + var header = new FrameHeader(); + BinaryReader br = new BinaryReader(ms); + //frame + var size = br.ReadUInt16(); + if (packet.Length != size || size < 36) + throw new Exception("Invalid packet"); + var a = br.ReadUInt16(); //origin:2, reserved:1, addressable:1, protocol:12 + var source = br.ReadUInt32(); + //frame address + byte[] target = br.ReadBytes(8); header.TargetMacAddress = target; - ms.Seek(6, SeekOrigin.Current); //skip reserved - var b = br.ReadByte(); //reserved:6, ack_required:1, res_required:1, - header.Sequence = br.ReadByte(); - //protocol header - var nanoseconds = br.ReadUInt64(); + ms.Seek(6, SeekOrigin.Current); //skip reserved + var b = br.ReadByte(); //reserved:6, ack_required:1, res_required:1, + header.Sequence = br.ReadByte(); + //protocol header + var nanoseconds = br.ReadUInt64(); header.AtTime = Utilities.Epoch.AddMilliseconds(nanoseconds * 0.000001); - var type = (MessageType)br.ReadUInt16(); - ms.Seek(2, SeekOrigin.Current); //skip reserved - return LifxResponse.Create(header, type, source, size > 36 ? br.ReadBytes(size - 36) : new byte[] { }); - } - } + var type = (MessageType)br.ReadUInt16(); + ms.Seek(2, SeekOrigin.Current); //skip reserved + return LifxResponse.Create(header, type, source, size > 36 ? br.ReadBytes(size - 36) : new byte[] { }); + } + } + + private void WritePacketToStream(Stream outStream, FrameHeader header, UInt16 type, byte[] payload) + { + using (var dw = new BinaryWriter(outStream) { /*ByteOrder = ByteOrder.LittleEndian*/ }) + { + //BinaryWriter bw = new BinaryWriter(ms); + #region Frame + //size uint16 + dw.Write((UInt16)((payload != null ? payload.Length : 0) + 36)); //length + // origin (2 bits, must be 0), reserved (1 bit, must be 0), addressable (1 bit, must be 1), protocol 12 bits must be 0x400) = 0x1400 + dw.Write((UInt16)0x3400); //protocol + dw.Write(ClientSource); //source identifier - unique value set by the client, used by responses. If 0, responses are broadcasted instead + #endregion Frame - private void WritePacketToStream(Stream outStream, FrameHeader header, UInt16 type, byte[] payload) - { - using (var dw = new BinaryWriter(outStream) { /*ByteOrder = ByteOrder.LittleEndian*/ }) - { - //BinaryWriter bw = new BinaryWriter(ms); - #region Frame - //size uint16 - dw.Write((UInt16)((payload != null ? payload.Length : 0) + 36)); //length - // origin (2 bits, must be 0), reserved (1 bit, must be 0), addressable (1 bit, must be 1), protocol 12 bits must be 0x400) = 0x1400 - dw.Write((UInt16)0x3400); //protocol - dw.Write((UInt32)header.Identifier); //source identifier - unique value set by the client, used by responses. If 0, responses are broadcasted instead - #endregion Frame + #region Frame address + //The target device address is 8 bytes long, when using the 6 byte MAC address then left - + //justify the value and zero-fill the last two bytes. A target device address of all zeroes effectively addresses all devices on the local network + dw.Write(header.TargetMacAddress); // target mac address - 0 means all devices + dw.Write(new byte[] { 0, 0, 0, 0, 0, 0 }); //reserved 1 - #region Frame address - //The target device address is 8 bytes long, when using the 6 byte MAC address then left - - //justify the value and zero-fill the last two bytes. A target device address of all zeroes effectively addresses all devices on the local network - dw.Write(header.TargetMacAddress); // target mac address - 0 means all devices - dw.Write(new byte[] { 0, 0, 0, 0, 0, 0 }); //reserved 1 + //The client can use acknowledgements to determine that the LIFX device has received a message. + //However, when using acknowledgements to ensure reliability in an over-burdened lossy network ... + //causing additional network packets may make the problem worse. + //Client that don't need to track the updated state of a LIFX device can choose not to request a + //response, which will reduce the network burden and may provide some performance advantage. In + //some cases, a device may choose to send a state update response independent of whether res_required is set. + if (header.AcknowledgeRequired && header.ResponseRequired) + dw.Write((byte)0x03); + else if (header.AcknowledgeRequired) + dw.Write((byte)0x02); + else if (header.ResponseRequired) + dw.Write((byte)0x01); + else + dw.Write((byte)0x00); + //The sequence number allows the client to provide a unique value, which will be included by the LIFX + //device in any message that is sent in response to a message sent by the client. This allows the client + //to distinguish between different messages sent with the same source identifier in the Frame. See + //ack_required and res_required fields in the Frame Address. + dw.Write((byte)header.Sequence); + #endregion Frame address - //The client can use acknowledgements to determine that the LIFX device has received a message. - //However, when using acknowledgements to ensure reliability in an over-burdened lossy network ... - //causing additional network packets may make the problem worse. - //Client that don't need to track the updated state of a LIFX device can choose not to request a - //response, which will reduce the network burden and may provide some performance advantage. In - //some cases, a device may choose to send a state update response independent of whether res_required is set. - if (header.AcknowledgeRequired && header.ResponseRequired) - dw.Write((byte)0x03); - else if (header.AcknowledgeRequired) - dw.Write((byte)0x02); - else if (header.ResponseRequired) - dw.Write((byte)0x01); - else - dw.Write((byte)0x00); - //The sequence number allows the client to provide a unique value, which will be included by the LIFX - //device in any message that is sent in response to a message sent by the client. This allows the client - //to distinguish between different messages sent with the same source identifier in the Frame. See - //ack_required and res_required fields in the Frame Address. - dw.Write((byte)header.Sequence); - #endregion Frame address + #region Protocol Header + //The at_time value should be zero for Set and Get messages sent by a client. + //For State messages sent by a device, the at_time will either be the device + //current time when the message was received or zero. StateColor is an example + //of a message that will return a non-zero at_time value + if (header.AtTime > DateTime.MinValue) + { + var time = header.AtTime.ToUniversalTime(); + dw.Write((UInt64)(time - new DateTime(1970, 01, 01)).TotalMilliseconds * 10); //timestamp + } + else + { + dw.Write((UInt64)0); + } + #endregion Protocol Header + dw.Write(type); //packet _type + dw.Write((UInt16)0); //reserved + if (payload != null) + dw.Write(payload); + dw.Flush(); + } + } + } - #region Protocol Header - //The at_time value should be zero for Set and Get messages sent by a client. - //For State messages sent by a device, the at_time will either be the device - //current time when the message was received or zero. StateColor is an example - //of a message that will return a non-zero at_time value - if (header.AtTime > DateTime.MinValue) - { - var time = header.AtTime.ToUniversalTime(); - dw.Write((UInt64)(time - new DateTime(1970, 01, 01)).TotalMilliseconds * 10); //timestamp - } - else - { - dw.Write((UInt64)0); - } - #endregion Protocol Header - dw.Write(type); //packet _type - dw.Write((UInt16)0); //reserved - if (payload != null) - dw.Write(payload); - dw.Flush(); - } - } - } + internal class FrameHeader + { + public byte Sequence; + public bool AcknowledgeRequired; + public bool ResponseRequired; + public byte[] TargetMacAddress; + public DateTime AtTime; - internal class FrameHeader - { - public UInt32 Identifier; - public byte Sequence; - public bool AcknowledgeRequired; - public bool ResponseRequired; - public byte[] TargetMacAddress; - public DateTime AtTime; - public FrameHeader() - { - Identifier = 0; - Sequence = 0; - AcknowledgeRequired = false; - ResponseRequired = false; - TargetMacAddress = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; - AtTime = DateTime.MinValue; - } + public FrameHeader() + { + Sequence = 0; + AcknowledgeRequired = false; + ResponseRequired = false; + TargetMacAddress = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + AtTime = DateTime.MinValue; + } public string TargetMacAddressName { get