private Task <T> BroadcastMessageAsync <T>(string hostName, FrameHeader header, MessageType type, params object[] args) where T : LifxResponse { List <byte> payload = new List <byte>(); 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 <T>(hostName, header, type, payload.ToArray())); }
/// <summary> /// Turns a bulb on or off using the provided transition time /// </summary> /// <param name="bulb"></param> /// <param name="transitionDuration"></param> /// <param name="isOn">True to turn on, false to turn off</param> /// <returns></returns> /// <seealso cref="TurnBulbOffAsync(LightBulb, TimeSpan)"/> /// <seealso cref="TurnBulbOnAsync(LightBulb, TimeSpan)"/> /// <seealso cref="TurnDeviceOnAsync(Device)"/> /// <seealso cref="TurnDeviceOffAsync(Device)"/> /// <seealso cref="SetDevicePowerStateAsync(Device, bool)"/> /// <seealso cref="GetLightPowerAsync(LightBulb)"/> public async Task SetLightPowerAsync(LightBulb bulb, TimeSpan transitionDuration, bool isOn) { if (bulb == null) { throw new ArgumentNullException(nameof(bulb)); } if (transitionDuration.TotalMilliseconds > uint.MaxValue || transitionDuration.Ticks < 0) { throw new ArgumentOutOfRangeException(nameof(transitionDuration)); } FrameHeader header = new FrameHeader(GetNextIdentifier(), true); var b = BitConverter.GetBytes((ushort)transitionDuration.TotalMilliseconds); Debug.WriteLine( $"Sending LightSetPower(on={isOn},duration={transitionDuration.TotalMilliseconds}ms) to {bulb.HostName}"); await BroadcastMessageAsync <AcknowledgementResponse>(bulb.HostName, header, MessageType.LightSetPower, (ushort)(isOn ? 65535 : 0), b ).ConfigureAwait(false); }
private async Task SetLightPowerAsync(LightBulb bulb, TimeSpan transitionDuration, bool isOn) { if (bulb == null) { throw new ArgumentNullException("bulb"); } if (transitionDuration.TotalMilliseconds > UInt32.MaxValue || transitionDuration.Ticks < 0) { throw new ArgumentOutOfRangeException("transitionDuration"); } FrameHeader header = new FrameHeader() { Identifier = (uint)randomizer.Next(), AcknowledgeRequired = true }; var b = BitConverter.GetBytes((UInt16)transitionDuration.TotalMilliseconds); await BroadcastMessageAsync <AcknowledgementResponse>(bulb.HostName, header, MessageType.LightSetPower, (UInt16)(isOn ? 65535 : 0), b ).ConfigureAwait(false); }
/// <summary> /// Sets color and temperature for a bulb and uses a transition time to the provided state /// </summary> /// <param name="bulb">Light bulb</param> /// <param name="hue">0..65535</param> /// <param name="saturation">0..65535</param> /// <param name="brightness">0..65535</param> /// <param name="kelvin">2700..9000</param> /// <param name="transitionDuration"></param> /// <returns></returns> public async Task SetColorAsync(LightBulb bulb, UInt16 hue, UInt16 saturation, UInt16 brightness, UInt16 kelvin, TimeSpan transitionDuration) { if (transitionDuration.TotalMilliseconds > UInt32.MaxValue || transitionDuration.Ticks < 0) { throw new ArgumentOutOfRangeException("transitionDuration"); } if (kelvin < 2500 || kelvin > 9000) { throw new ArgumentOutOfRangeException("kelvin", "Kelvin must be between 2500 and 9000"); } System.Diagnostics.Debug.WriteLine("Setting color to {0}", bulb.HostName); FrameHeader header = new FrameHeader() { Identifier = (uint)randomizer.Next(), AcknowledgeRequired = true }; UInt32 duration = (UInt32)transitionDuration.TotalMilliseconds; var durationBytes = BitConverter.GetBytes(duration); var h = BitConverter.GetBytes(hue); var s = BitConverter.GetBytes(saturation); var b = BitConverter.GetBytes(brightness); var k = BitConverter.GetBytes(kelvin); await BroadcastMessageAsync <AcknowledgementResponse>(bulb.HostName, header, MessageType.LightSetColor, (byte)0x00, //reserved hue, saturation, brightness, kelvin, //HSBK duration ); }
/// <summary> /// Request an arbitrary payload be echoed back. /// </summary> /// <param name="device"></param> /// <param name="payload"></param> /// <returns><see cref="EchoResponse"/></returns> /// <exception cref="ArrayTypeMismatchException"></exception> public async Task <EchoResponse> RequestEcho(Device device, byte[] payload) { if (device == null) { throw new ArrayTypeMismatchException(nameof(device)); } FrameHeader header = new FrameHeader(GetNextIdentifier()); // Truncate our input payload to be 64 bits exactly var realPayload = new byte[64]; for (var i = 0; i < realPayload.Length; i++) { if (i < payload.Length) { realPayload[i] = payload[i]; } else { realPayload[i] = 0; } } return(await BroadcastMessageAsync <EchoResponse>(device.HostName, header, MessageType.DeviceEchoRequest, realPayload)); }
internal StateServiceResponse(FrameHeader header, MessageType type, byte[] payload, UInt32 source) : base(header, type, payload, source) { Service = payload[0]; Port = BitConverter.ToUInt32(payload, 1); }
internal AcknowledgementResponse(FrameHeader header, MessageType type, byte[] payload, UInt32 source) : base(header, type, payload, source) { }
internal UnknownResponse(FrameHeader header, MessageType type, byte[] payload, UInt32 source) : base(header, type, payload, source) { }
internal StateVersionResponse(FrameHeader header, MessageType type, byte[] payload, UInt32 source) : base(header, type, payload, source) { Vendor = BitConverter.ToUInt32(payload, 0); Product = BitConverter.ToUInt32(payload, 4); Version = BitConverter.ToUInt32(payload, 8); }
internal InfraredStateRespone(FrameHeader header, MessageType type, byte[] payload, UInt32 source) : base(header, type, payload, source) { Brightness = BitConverter.ToUInt16(payload, 0); }
internal LightPowerResponse(FrameHeader header, MessageType type, byte[] payload, UInt32 source) : base(header, type, payload, source) { IsOn = BitConverter.ToUInt16(payload, 0) > 0; }
private async Task WritePacketToStreamAsync(Stream outStream, FrameHeader header, UInt16 type, byte[] payload) { using (var dw = new BinaryWriter(outStream) /*ByteOrder = ByteOrder.LittleEndian*/ }
private async Task <T> BroadcastMessagePayloadAsync <T>(string hostName, FrameHeader header, MessageType type, byte[] payload) where T : LifxResponse { #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())); #endif if (hostName == null) { hostName = "255.255.255.255"; } TaskCompletionSource <T> tcs = null; if (//header.AcknowledgeRequired && header.Identifier > 0 && typeof(T) != typeof(UnknownResponse)) { tcs = new TaskCompletionSource <T>(); Action <LifxResponse> action = (r) => { if (!tcs.Task.IsCompleted) { if (r.GetType() == typeof(T)) { tcs.SetResult((T)r); } else { } } }; taskCompletions[header.Identifier] = action; } using (MemoryStream stream = new MemoryStream()) { await WritePacketToStreamAsync(stream, header, (UInt16)type, payload).ConfigureAwait(false); 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); }
private async Task WritePacketToStreamAsync(IOutputStream outStream, FrameHeader header, UInt16 type, byte[] payload) { using (DataWriter dw = new DataWriter(outStream) { ByteOrder = ByteOrder.LittleEndian }) { //BinaryWriter bw = new BinaryWriter(ms); #region Frame //size uint16 dw.WriteUInt16((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.WriteUInt16(0x3400); //protocol dw.WriteUInt32(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.WriteBytes(header.TargetMacAddress); // target mac address - 0 means all devices dw.WriteBytes(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.WriteByte(0x03); } else if (header.AcknowledgeRequired) { dw.WriteByte(0x02); } else if (header.ResponseRequired) { dw.WriteByte(0x01); } else { dw.WriteByte(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.WriteByte(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.WriteUInt64((UInt64)(time - new DateTime(1970, 01, 01)).TotalMilliseconds * 10); //timestamp } else { dw.WriteUInt64(0); } #endregion Protocol Header dw.WriteUInt16(type); //packet _type dw.WriteUInt16(0); //reserved if (payload != null) { dw.WriteBytes(payload); } await dw.StoreAsync(); } }
private async Task <T> BroadcastMessagePayloadAsync <T>(string hostName, FrameHeader header, MessageType type, byte[] payload) where T : LifxResponse { uint attemptCount = 5; T result = default(T); do { if (hostName == null) { hostName = "255.255.255.255"; } TaskCompletionSource <T> tcs = null; if (//header.AcknowledgeRequired && header.Identifier > 0 && typeof(T) != typeof(UnknownResponse)) { tcs = new TaskCompletionSource <T>(); Action <LifxResponse> action = (r) => { if (!tcs.Task.IsCompleted) { if (r.GetType() == typeof(T)) { tcs.SetResult((T)r); } } }; taskCompletions[header.Identifier] = action; } using (MemoryStream stream = new MemoryStream()) { WritePacketToStreamAsync(stream, header, (UInt16)type, payload); var msg = stream.ToArray(); await _socket.SendAsync(msg, msg.Length, hostName, Port); } if (tcs != null) { var _ = Task.Delay(1000).ContinueWith((t) => { if (!tcs.Task.IsCompleted) { tcs.TrySetException(new TimeoutException()); } }); try { result = await tcs.Task.ConfigureAwait(false); } catch { } finally { taskCompletions.Remove(header.Identifier); } } attemptCount--; } while (result == null && attemptCount > 0); return(result); }