diff --git a/README.md b/README.md index 70dc686e..eef6bc2a 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,11 @@ It's not trivial to convert the light colors to a color system developers like t ## Remote API There is also a Philips Hue Remote API. It allows you to send commands to a bridge over the internet. You can request access here: http://www.developers.meethue.com/content/remote-api Q42.HueApi is compatible with the remote API. -You need an Access Token and a Bridge Id. Please refer to the Philips Hue API documentation on how to obtain them. This library does not have support for it yet. Pull Requests are welcome! +You need an Access Token and a Bridge Id. Please refer to the Philips Hue API documentation on how to obtain them. +**NOTE** Remote API support is untested. Testing and PRs are very much appreciated. IRemoteHueClient remoteHueClient = new RemoteHueClient("access token"); - remoteHueClient.Initialize("bridge id"); + remoteHueClient.Initialize("bridge id", "whitelist_id"); After the setup, you can send normal commands to the remote API: diff --git a/src/Q42.HueApi.Tests/Q42.HueApi.Tests.csproj b/src/Q42.HueApi.Tests/Q42.HueApi.Tests.csproj index faeae91d..087bef33 100644 --- a/src/Q42.HueApi.Tests/Q42.HueApi.Tests.csproj +++ b/src/Q42.HueApi.Tests/Q42.HueApi.Tests.csproj @@ -87,6 +87,10 @@ + + {C7305601-B583-479D-9A08-D6DA0A63AB85} + Q42.HueApi.ColorConverters + {e59a16b4-6756-4576-ab63-6a9b2cf2892b} Q42.HueApi diff --git a/src/Q42.HueApi.Tests/Remote/RemoteLightsTests.cs b/src/Q42.HueApi.Tests/Remote/RemoteLightsTests.cs index 32368c87..40ba5a08 100644 --- a/src/Q42.HueApi.Tests/Remote/RemoteLightsTests.cs +++ b/src/Q42.HueApi.Tests/Remote/RemoteLightsTests.cs @@ -1,4 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using Q42.HueApi.Interfaces; using System; using System.Collections.Generic; using System.Linq; @@ -13,12 +14,20 @@ public class RemoteLightsTests : LightsTests [TestInitialize] public new void Initialize() { - var remoteBridge = new RemoteHueClient("test"); - //remoteBridge.Initialize("bridgeId"); + IRemoteHueClient remoteBridge = new RemoteHueClient("test"); + remoteBridge.Initialize("bridgeId", "key"); _client = remoteBridge; } + [TestMethod] + public async Task RegisterBridgeTest() + { + await ((IRemoteHueClient)_client).RegisterAsync("1", "test"); + var result = await _client.GetLightAsync("1"); + Assert.AreEqual("test", result.Name); + } + [TestMethod] public async Task SetLightNameTest() { diff --git a/src/Q42.HueApi.WinRT.Sample/ViewModel/MainViewModel.cs b/src/Q42.HueApi.WinRT.Sample/ViewModel/MainViewModel.cs index 10da6266..6787b8df 100644 --- a/src/Q42.HueApi.WinRT.Sample/ViewModel/MainViewModel.cs +++ b/src/Q42.HueApi.WinRT.Sample/ViewModel/MainViewModel.cs @@ -173,7 +173,7 @@ private async void GetLightsAction() private void RedAction() { LightCommand command = new LightCommand(); - command.TurnOn().SetColor(new RGBColor("FF0000)")); + command.TurnOn().SetColor(new RGBColor("FF0000")); _hueClient.SendCommandAsync(command); } diff --git a/src/Q42.HueApi/HueClient.cs b/src/Q42.HueApi/HueClient.cs index 0050eb4b..474edbc4 100644 --- a/src/Q42.HueApi/HueClient.cs +++ b/src/Q42.HueApi/HueClient.cs @@ -24,26 +24,45 @@ public partial class HueClient : IHueClient private readonly int _parallelRequests = 5; + /// + /// Whitelist ID + /// + protected string _appKey; + + /// /// Indicates the HueClient is initialized with an AppKey /// public bool IsInitialized { get; protected set; } - protected HueClient() - { + protected virtual string ApiBase { get; private set; } - } + protected static HttpClient _httpClient; - protected virtual string ApiBase { get; private set; } + protected HueClient() + { - [ThreadStatic] - protected static HttpClient _httpClient; - - public static HttpClient GetHttpClient() + } + + /// + /// Initialize client with your app key + /// + /// + public void Initialize(string appKey) + { + if (appKey == null) + throw new ArgumentNullException(nameof(appKey)); + + _appKey = appKey; + + IsInitialized = true; + } + + public static HttpClient GetHttpClient() { // return per-thread HttpClient - if (_httpClient == null) - _httpClient = new HttpClient(); + if (_httpClient == null) + _httpClient = new HttpClient(); return _httpClient; } diff --git a/src/Q42.HueApi/Interfaces/IHueClient.cs b/src/Q42.HueApi/Interfaces/IHueClient.cs index a4e8f86d..4cfd41c3 100644 --- a/src/Q42.HueApi/Interfaces/IHueClient.cs +++ b/src/Q42.HueApi/Interfaces/IHueClient.cs @@ -13,13 +13,14 @@ namespace Q42.HueApi.Interfaces /// public interface IHueClient { - - - /// - /// Asynchronously gets all lights registered with the bridge. - /// - /// An enumerable of s registered with the bridge. - Task> GetWhiteListAsync(); + + + + /// + /// Asynchronously gets all lights registered with the bridge. + /// + /// An enumerable of s registered with the bridge. + Task> GetWhiteListAsync(); /// /// Get bridge info diff --git a/src/Q42.HueApi/Interfaces/ILocalHueClient.cs b/src/Q42.HueApi/Interfaces/ILocalHueClient.cs index 4269cecb..08b4a634 100644 --- a/src/Q42.HueApi/Interfaces/ILocalHueClient.cs +++ b/src/Q42.HueApi/Interfaces/ILocalHueClient.cs @@ -13,12 +13,6 @@ namespace Q42.HueApi.Interfaces /// public interface ILocalHueClient : IHueClient { - /// - /// Initialize the client with your app key - /// - /// - void Initialize(string appKey); - /// /// Register your and at the Hue Bridge. /// @@ -29,11 +23,17 @@ public interface ILocalHueClient : IHueClient /// or aren't long enough, are empty or contains spaces. Task RegisterAsync(string appName, string appKey); - /// - /// Check if there is a working connection with the bridge - /// - /// - Task CheckConnection(); + /// + /// Initialize the client with your app key + /// + /// + void Initialize(string appKey); + + /// + /// Check if there is a working connection with the bridge + /// + /// + Task CheckConnection(); } diff --git a/src/Q42.HueApi/Interfaces/IRemoteHueClient.cs b/src/Q42.HueApi/Interfaces/IRemoteHueClient.cs index 1b0c0009..04688555 100644 --- a/src/Q42.HueApi/Interfaces/IRemoteHueClient.cs +++ b/src/Q42.HueApi/Interfaces/IRemoteHueClient.cs @@ -2,11 +2,50 @@ namespace Q42.HueApi.Interfaces { + /// + /// Remote Hue Client responsible for interacting with the bridge using the remote API + /// public interface IRemoteHueClient : IHueClient { + /// + /// Untested + /// + /// + /// + /// + /// + /// + /// + /// Task Authorize(string clientId, string state, string deviceId, string appId, string deviceName = null, string responseType = "code"); - void Initialize(string bridgeId); - Task RefreshToken(string refreshToken, string clientId, string clientSecret); + + /// + /// Initialize the client with a bridgeId and appKey (whitelist identifier) + /// + /// + /// + void Initialize(string bridgeId, string appKey); + + /// + /// Registers bridge for remote communication. Returns appKey and Initialized the client with this appkey + /// + /// + /// + Task RegisterAsync(string bridgeId, string appId); + + /// + /// Set the accessToken for the RemoteHueClient + /// + /// void SetRemoteAccessToken(string accessToken); + + /// + /// Untested + /// + /// + /// + /// + /// + Task RefreshToken(string refreshToken, string clientId, string clientSecret); } } \ No newline at end of file diff --git a/src/Q42.HueApi/LocalHueClient.cs b/src/Q42.HueApi/LocalHueClient.cs index bbd43c73..30a217d8 100644 --- a/src/Q42.HueApi/LocalHueClient.cs +++ b/src/Q42.HueApi/LocalHueClient.cs @@ -24,7 +24,6 @@ public partial class LocalHueClient : HueClient, ILocalHueClient private readonly string _ip; - private string _appKey; /// @@ -86,19 +85,7 @@ public LocalHueClient(string ip, string appKey) } - /// - /// Initialize client with your app key - /// - /// - public void Initialize(string appKey) - { - if (appKey == null) - throw new ArgumentNullException(nameof(appKey)); - - _appKey = appKey; - - IsInitialized = true; - } + } } diff --git a/src/Q42.HueApi/RemoteHueClient-Authentication.cs b/src/Q42.HueApi/RemoteHueClient-Authentication.cs index d93bf61d..dfc57989 100644 --- a/src/Q42.HueApi/RemoteHueClient-Authentication.cs +++ b/src/Q42.HueApi/RemoteHueClient-Authentication.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json.Linq; +using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -65,5 +66,53 @@ public async Task RefreshToken(string refreshToken, string clientId, str return string.Empty; } + + + public async Task RegisterAsync(string bridgeId, string appId) + { + if (string.IsNullOrEmpty(bridgeId)) + throw new ArgumentNullException(nameof(bridgeId)); + if (string.IsNullOrEmpty(appId)) + throw new ArgumentNullException(nameof(appId)); + + JObject obj = new JObject(); + obj["linkbutton"] = true; + + HttpClient client = HueClient.GetHttpClient(); + var configResponse = await client.PutAsync(new Uri($"{_apiBase}{bridgeId}/0/config"), new StringContent(obj.ToString())).ConfigureAwait(false); + + JObject bridge = new JObject(); + bridge["devicetype"] = appId; + + var response = await client.PostAsync(new Uri($"{_apiBase}{bridgeId}/"), new StringContent(bridge.ToString())).ConfigureAwait(false); + var stringResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + + + JObject result; + try + { + JArray jresponse = JArray.Parse(stringResponse); + result = (JObject)jresponse.First; + } + catch + { + //Not an expected response. Return response as exception + throw new Exception(stringResponse); + } + + JToken error; + if (result.TryGetValue("error", out error)) + { + if (error["type"].Value() == 101) // link button not pressed + throw new Exception("Link button not pressed"); + else + throw new Exception(error["description"].Value()); + } + + var key = result["success"]["username"].Value(); + Initialize(key); + + return key; + } } } diff --git a/src/Q42.HueApi/RemoteHueClient.cs b/src/Q42.HueApi/RemoteHueClient.cs index d0d73b70..a697c3aa 100644 --- a/src/Q42.HueApi/RemoteHueClient.cs +++ b/src/Q42.HueApi/RemoteHueClient.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; @@ -10,6 +11,7 @@ namespace Q42.HueApi { public partial class RemoteHueClient : HueClient, IRemoteHueClient { + private readonly string _apiBase = "https://api.meethue.com/v2/bridges/"; private static string _remoteAccessToken; private string _bridgeId; @@ -25,23 +27,23 @@ public void SetRemoteAccessToken(string accessToken) var client = GetHttpClient(); if (!string.IsNullOrEmpty(_remoteAccessToken)) - _httpClient.DefaultRequestHeaders.Add("access_token", _remoteAccessToken); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _remoteAccessToken); else - _httpClient.DefaultRequestHeaders.Remove("access_token"); + _httpClient.DefaultRequestHeaders.Authorization = null; } /// /// Initialize client with your app key /// /// - public void Initialize(string bridgeId) + public void Initialize(string bridgeId, string appKey) { if (bridgeId == null) throw new ArgumentNullException(nameof(bridgeId)); _bridgeId = bridgeId; - IsInitialized = true; + Initialize(appKey); } @@ -53,7 +55,7 @@ public void Initialize(string bridgeId) _httpClient = new HttpClient(); if (!string.IsNullOrEmpty(_remoteAccessToken)) - _httpClient.DefaultRequestHeaders.Add("access_token", _remoteAccessToken); + _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _remoteAccessToken); } return _httpClient; @@ -67,7 +69,7 @@ protected override string ApiBase { get { - return string.Format("https://api.meethue.com/v1/bridges/{0}/", _bridgeId); + return $"{_apiBase}{_bridgeId}/{_appKey}/"; } } }