From 4724335e0c054f98177bb1d251da5a47bea9b893 Mon Sep 17 00:00:00 2001 From: Bart de Bever Date: Thu, 27 May 2021 21:41:29 +0200 Subject: [PATCH] Reworked the entire API layer to use DTO classes and a factory instead of hacky creation of the client. --- Winleafs.Api.Test/Program.cs | 3 +- Winleafs.Api/ClientFactory.cs | 71 +++++ .../DTOs/Authentication/AuthenticationDto.cs | 10 + Winleafs.Api/DTOs/ClientDto.cs | 29 ++ .../DTOs/Effects/GetEffectDetailsDto.cs | 14 + Winleafs.Api/DTOs/Effects/SelectEffectDto.cs | 12 + .../ExternalControl/ExternalControlDto.cs | 19 ++ Winleafs.Api/DTOs/Shared/WriteCommands.cs | 9 + Winleafs.Api/DTOs/Shared/WriteObjectDto.cs | 24 ++ Winleafs.Api/DTOs/States/SetStateDto.cs | 12 + Winleafs.Api/DTOs/States/SetStateValuesDto.cs | 55 ++++ Winleafs.Api/DTOs/States/SetValueDto.cs | 12 + .../DTOs/States/SetValueWithDurationDto.cs | 15 + Winleafs.Api/DTOs/States/StateValueDto.cs | 12 + .../Endpoints/AuthorizationEndpoint.cs | 54 ++-- Winleafs.Api/Endpoints/EffectsEndpoint.cs | 25 +- .../Endpoints/ExternalControlEndpoint.cs | 15 +- Winleafs.Api/Endpoints/IdentifyEndpoint.cs | 4 +- Winleafs.Api/Endpoints/LayoutEndpoint.cs | 3 +- Winleafs.Api/Endpoints/NanoleafEndpoint.cs | 20 +- Winleafs.Api/Endpoints/StateEndpoint.cs | 284 +++++++++--------- .../Exceptions/InvalidDeviceException.cs | 25 ++ Winleafs.Api/NanoleafClient.cs | 67 +---- Winleafs.Api/Winleafs.Api.csproj | 1 + .../Api/Effects/CustomEffectsCollection.cs | 2 +- .../Api/Effects/ScreenMirrorEffect.cs | 2 +- .../{Ambilght.cs => Ambilight.cs} | 4 +- Winleafs.Wpf/Api/Layouts/PanelLayout.cs | 2 +- Winleafs.Wpf/Api/Orchestrator.cs | 2 +- Winleafs.Wpf/Api/ScheduleTimer.cs | 2 +- Winleafs.Wpf/Views/App.xaml.cs | 2 +- .../Views/Effects/EffectComboBox.xaml.cs | 2 +- .../Layout/LayoutDisplayUserControl.xaml.cs | 2 +- .../Views/MainWindows/MainWindow.xaml.cs | 2 +- Winleafs.Wpf/Views/Setup/SetupWindow.xaml.cs | 5 +- 35 files changed, 543 insertions(+), 279 deletions(-) create mode 100644 Winleafs.Api/ClientFactory.cs create mode 100644 Winleafs.Api/DTOs/Authentication/AuthenticationDto.cs create mode 100644 Winleafs.Api/DTOs/ClientDto.cs create mode 100644 Winleafs.Api/DTOs/Effects/GetEffectDetailsDto.cs create mode 100644 Winleafs.Api/DTOs/Effects/SelectEffectDto.cs create mode 100644 Winleafs.Api/DTOs/ExternalControl/ExternalControlDto.cs create mode 100644 Winleafs.Api/DTOs/Shared/WriteCommands.cs create mode 100644 Winleafs.Api/DTOs/Shared/WriteObjectDto.cs create mode 100644 Winleafs.Api/DTOs/States/SetStateDto.cs create mode 100644 Winleafs.Api/DTOs/States/SetStateValuesDto.cs create mode 100644 Winleafs.Api/DTOs/States/SetValueDto.cs create mode 100644 Winleafs.Api/DTOs/States/SetValueWithDurationDto.cs create mode 100644 Winleafs.Api/DTOs/States/StateValueDto.cs create mode 100644 Winleafs.Api/Exceptions/InvalidDeviceException.cs rename Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/{Ambilght.cs => Ambilight.cs} (96%) diff --git a/Winleafs.Api.Test/Program.cs b/Winleafs.Api.Test/Program.cs index d1a744a0..4757a4d2 100644 --- a/Winleafs.Api.Test/Program.cs +++ b/Winleafs.Api.Test/Program.cs @@ -1,4 +1,5 @@ using System; +using Winleafs.Api.DTOs; namespace Winleafs.Api.Test { @@ -6,7 +7,7 @@ class Program { static void Main(string[] args) { - var nanoLeafClient = new NanoleafClient("192.168.178.160", 16021); + var nanoLeafClient = new NanoleafClient(new ClientDto("192.168.178.160", 16021)); Console.WriteLine("Authorizing.."); nanoLeafClient.AuthorizationEndpoint.GetAuthTokenAsync().GetAwaiter().GetResult(); Console.WriteLine("Authorized!"); diff --git a/Winleafs.Api/ClientFactory.cs b/Winleafs.Api/ClientFactory.cs new file mode 100644 index 00000000..c5b4ca4c --- /dev/null +++ b/Winleafs.Api/ClientFactory.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using Winleafs.Api.DTOs; +using Winleafs.Api.Exceptions; +using Winleafs.Models.Models; + +namespace Winleafs.Api +{ + public class ClientFactory + { + private readonly Dictionary _clients = new Dictionary(); + + public static ClientFactory Instance { get; } = new ClientFactory(); + + private ClientFactory() + { + } + + public INanoleafClient Get(Device device) + { + + if (device == null || string.IsNullOrWhiteSpace(device.IPAddress) || device.Port == default) + { + throw new InvalidDeviceException("The provided device does not have a valid IP or port."); + } + + var key = GetIdentifier(device.IPAddress, device.Port); + if (_clients.ContainsKey(key)) + { + return _clients[key]; + } + + var client = new NanoleafClient(new ClientDto(device)); + _clients.Add(key, client); + return client; + } + + public INanoleafClient Get(string ip, int port, string authenticationToken = null) + { + if (string.IsNullOrWhiteSpace(ip) || port == default) + { + throw new InvalidDeviceException("The provided device does not have a valid IP or port."); + } + + var key = GetIdentifier(ip, port); + if (_clients.ContainsKey(key)) + { + return _clients[key]; + } + + var client = new NanoleafClient(new ClientDto(ip, port, authenticationToken)); + _clients.Add(key, client); + return client; + } + + public bool Delete(Device device) + { + if (device == null) + { + return false; + } + + var key = GetIdentifier(device.IPAddress, device.Port); + return _clients.Remove(key); + } + + private static string GetIdentifier(string ip, int port) + { + return ip + '-' + port; + } + } +} diff --git a/Winleafs.Api/DTOs/Authentication/AuthenticationDto.cs b/Winleafs.Api/DTOs/Authentication/AuthenticationDto.cs new file mode 100644 index 00000000..02b7670c --- /dev/null +++ b/Winleafs.Api/DTOs/Authentication/AuthenticationDto.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json; + +namespace Winleafs.Api.DTOs.Authentication +{ + public class AuthenticationDto + { + [JsonProperty("auth_token")] + public string AuthenticationToken { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/ClientDto.cs b/Winleafs.Api/DTOs/ClientDto.cs new file mode 100644 index 00000000..5217f53b --- /dev/null +++ b/Winleafs.Api/DTOs/ClientDto.cs @@ -0,0 +1,29 @@ +using Winleafs.Models.Models; + +namespace Winleafs.Api.DTOs +{ + public class ClientDto + { + public ClientDto(string ip, int port, string authenticationToken = null) + { + Ip = ip; + Port = port; + AuthenticationToken = authenticationToken; + } + + public ClientDto(Device device) + { + Ip = device.IPAddress; + Port = device.Port; + AuthenticationToken = device.AuthToken; + } + + public string Ip { get; set; } + + public int Port { get; set; } + + public string AuthenticationToken { get; set; } + + public string BaseUrl => $"http://{Ip}:{Port}/api/v1/{AuthenticationToken}/"; + } +} diff --git a/Winleafs.Api/DTOs/Effects/GetEffectDetailsDto.cs b/Winleafs.Api/DTOs/Effects/GetEffectDetailsDto.cs new file mode 100644 index 00000000..b43bde99 --- /dev/null +++ b/Winleafs.Api/DTOs/Effects/GetEffectDetailsDto.cs @@ -0,0 +1,14 @@ +using Winleafs.Api.DTOs.Shared; + +namespace Winleafs.Api.DTOs.Effects +{ + public class GetEffectDetailsDto + { + public GetEffectDetailsDto(string effectName) + { + Write = new WriteObjectDto(WriteCommands.Request, effectName); + } + + public WriteObjectDto Write { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/Effects/SelectEffectDto.cs b/Winleafs.Api/DTOs/Effects/SelectEffectDto.cs new file mode 100644 index 00000000..1595dba9 --- /dev/null +++ b/Winleafs.Api/DTOs/Effects/SelectEffectDto.cs @@ -0,0 +1,12 @@ +namespace Winleafs.Api.DTOs.Effects +{ + public class SelectEffectDto + { + public SelectEffectDto(string select) + { + Select = select; + } + + public string Select { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/ExternalControl/ExternalControlDto.cs b/Winleafs.Api/DTOs/ExternalControl/ExternalControlDto.cs new file mode 100644 index 00000000..b4a9a9c0 --- /dev/null +++ b/Winleafs.Api/DTOs/ExternalControl/ExternalControlDto.cs @@ -0,0 +1,19 @@ +using Winleafs.Api.DTOs.Shared; + +namespace Winleafs.Api.DTOs.ExternalControl +{ + public class ExternalControlDto + { + public ExternalControlDto(string version) + { + Write = new WriteObjectDto() + { + Command = WriteCommands.Display, + AnimType = "extControl", + ExtControlVersion = version + }; + } + + public WriteObjectDto Write { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/Shared/WriteCommands.cs b/Winleafs.Api/DTOs/Shared/WriteCommands.cs new file mode 100644 index 00000000..aa56f58b --- /dev/null +++ b/Winleafs.Api/DTOs/Shared/WriteCommands.cs @@ -0,0 +1,9 @@ +namespace Winleafs.Api.DTOs.Shared +{ + public static class WriteCommands + { + public static string Request => "request"; + + public static string Display => "display"; + } +} diff --git a/Winleafs.Api/DTOs/Shared/WriteObjectDto.cs b/Winleafs.Api/DTOs/Shared/WriteObjectDto.cs new file mode 100644 index 00000000..1775b68f --- /dev/null +++ b/Winleafs.Api/DTOs/Shared/WriteObjectDto.cs @@ -0,0 +1,24 @@ +namespace Winleafs.Api.DTOs.Shared +{ + public class WriteObjectDto + { + public string Command { get; set; } + + public string AnimName { get; set; } + + public string AnimType { get; set; } + + public string ExtControlVersion { get; set; } + + public WriteObjectDto() + { + } + + public WriteObjectDto(string command, string animName, string extControlVersion = null) + { + Command = command; + AnimName = animName; + ExtControlVersion = extControlVersion; + } + } +} diff --git a/Winleafs.Api/DTOs/States/SetStateDto.cs b/Winleafs.Api/DTOs/States/SetStateDto.cs new file mode 100644 index 00000000..c85531ff --- /dev/null +++ b/Winleafs.Api/DTOs/States/SetStateDto.cs @@ -0,0 +1,12 @@ +namespace Winleafs.Api.DTOs.States +{ + public class SetStateDto + { + public SetStateDto(bool value) + { + On = new StateValueDto(value); + } + + public StateValueDto On { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/States/SetStateValuesDto.cs b/Winleafs.Api/DTOs/States/SetStateValuesDto.cs new file mode 100644 index 00000000..ec5451b7 --- /dev/null +++ b/Winleafs.Api/DTOs/States/SetStateValuesDto.cs @@ -0,0 +1,55 @@ +using Newtonsoft.Json; + +namespace Winleafs.Api.DTOs.States +{ + public class SetStateValuesDto + { + public SetStateValuesDto() + { + } + + public SetStateValuesDto(int hue, int saturation) + { + Hue = new SetValueDto(hue); + Saturation = new SetValueDto(saturation); + } + + public SetStateValuesDto(int hue, int saturation, int brightness) + { + Brightness = new SetValueWithDurationDto(brightness, null); + Hue = new SetValueDto(hue); + Saturation = new SetValueDto(saturation); + } + + public SetValueDto Hue { get; set; } + + [JsonProperty("sat")] + public SetValueDto Saturation { get; set; } + + public SetValueWithDurationDto Brightness { get; set; } + + public static SetStateValuesDto SetBrightness(int value, int? duration = null) + { + return new SetStateValuesDto() + { + Brightness = new SetValueWithDurationDto(value, duration) + }; + } + + public static SetStateValuesDto SetHue(int value) + { + return new SetStateValuesDto() + { + Hue = new SetValueDto(value) + }; + } + + public static SetStateValuesDto SetSaturation(int value) + { + return new SetStateValuesDto() + { + Saturation = new SetValueDto(value) + }; + } + } +} diff --git a/Winleafs.Api/DTOs/States/SetValueDto.cs b/Winleafs.Api/DTOs/States/SetValueDto.cs new file mode 100644 index 00000000..f0ee013c --- /dev/null +++ b/Winleafs.Api/DTOs/States/SetValueDto.cs @@ -0,0 +1,12 @@ +namespace Winleafs.Api.DTOs.States +{ + public class SetValueDto + { + public SetValueDto(int value) + { + Value = value; + } + + public int Value { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/States/SetValueWithDurationDto.cs b/Winleafs.Api/DTOs/States/SetValueWithDurationDto.cs new file mode 100644 index 00000000..aeb28a6f --- /dev/null +++ b/Winleafs.Api/DTOs/States/SetValueWithDurationDto.cs @@ -0,0 +1,15 @@ +namespace Winleafs.Api.DTOs.States +{ + public class SetValueWithDurationDto + { + public SetValueWithDurationDto(int value, int? duration = null) + { + Value = value; + Duration = duration; + } + + public int Value { get; set; } + + public int? Duration { get; set; } + } +} diff --git a/Winleafs.Api/DTOs/States/StateValueDto.cs b/Winleafs.Api/DTOs/States/StateValueDto.cs new file mode 100644 index 00000000..f1a0165f --- /dev/null +++ b/Winleafs.Api/DTOs/States/StateValueDto.cs @@ -0,0 +1,12 @@ +namespace Winleafs.Api.DTOs.States +{ + public class StateValueDto + { + public StateValueDto(bool value) + { + Value = value; + } + + public bool Value { get; set; } + } +} diff --git a/Winleafs.Api/Endpoints/AuthorizationEndpoint.cs b/Winleafs.Api/Endpoints/AuthorizationEndpoint.cs index c2044df4..f163d4d2 100644 --- a/Winleafs.Api/Endpoints/AuthorizationEndpoint.cs +++ b/Winleafs.Api/Endpoints/AuthorizationEndpoint.cs @@ -1,38 +1,44 @@ using System.Threading.Tasks; - -using Newtonsoft.Json.Linq; - +using Newtonsoft.Json; using RestSharp; - +using RestSharp.Serializers.NewtonsoftJson; +using Winleafs.Api.DTOs; +using Winleafs.Api.DTOs.Authentication; using Winleafs.Api.Endpoints.Interfaces; namespace Winleafs.Api.Endpoints { - public class AuthorizationEndpoint : NanoleafEndpoint, IAuthorizationEndpoint - { - /// - public AuthorizationEndpoint(NanoleafClient client) - { - Client = client; - } + public class AuthorizationEndpoint : NanoleafEndpoint, IAuthorizationEndpoint + { + /// + public AuthorizationEndpoint(ClientDto client) + { + Client = client; + } - /// - public string GetAuthToken() + /// + public string GetAuthToken() { return GetAuthTokenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } - /// - public async Task GetAuthTokenAsync() - { - var client = new RestClient(Client.BaseUri); - var request = new RestRequest("api/v1/new", Method.POST); - var response = await client.ExecuteAsync(request).ConfigureAwait(false); + /// + public async Task GetAuthTokenAsync() + { + var client = new RestClient($"http://{Client.Ip}:{Client.Port}"); + client.UseNewtonsoftJson(); + var request = new RestRequest("api/v1/new", Method.POST); + var response = await client.ExecuteAsync(request).ConfigureAwait(false); + - var jObject = JObject.Parse(response.Content); - Client.Token = jObject["auth_token"].ToString(); + if (!response.IsSuccessful) + { + return null; + } - return Client.Token; - } - } + Client.AuthenticationToken = response.Data.AuthenticationToken; + + return Client.AuthenticationToken; + } + } } diff --git a/Winleafs.Api/Endpoints/EffectsEndpoint.cs b/Winleafs.Api/Endpoints/EffectsEndpoint.cs index 86870ecb..a0b87761 100644 --- a/Winleafs.Api/Endpoints/EffectsEndpoint.cs +++ b/Winleafs.Api/Endpoints/EffectsEndpoint.cs @@ -3,7 +3,8 @@ using System.Threading.Tasks; using RestSharp; - +using Winleafs.Api.DTOs; +using Winleafs.Api.DTOs.Effects; using Winleafs.Api.Endpoints.Interfaces; using Winleafs.Models.Models.Effects; @@ -14,7 +15,7 @@ public class EffectsEndpoint : NanoleafEndpoint, IEffectsEndpoint private const string BaseUrl = "effects"; /// - public EffectsEndpoint(NanoleafClient client) + public EffectsEndpoint(ClientDto client) { Client = client; } @@ -46,13 +47,13 @@ public string GetSelectedEffect() /// public Task SetSelectedEffectAsync(string effectName) { - return SendRequestAsync(BaseUrl, Method.PUT, body: new { select = effectName }); + return SendRequestAsync(BaseUrl, Method.PUT, body: new SelectEffectDto(effectName)); } /// public void SetSelectedEffect(string effectName) { - SendRequest(BaseUrl, Method.PUT, body: new { select = effectName}); + SendRequest(BaseUrl, Method.PUT, body: new SelectEffectDto(effectName)); } @@ -64,7 +65,7 @@ public Task GetEffectDetailsAsync(string effectName) return Task.FromResult((Effect)null); } - return SendRequestAsync(BaseUrl, Method.PUT, CreateWriteEffectCommand(effectName)); + return SendRequestAsync(BaseUrl, Method.PUT, new GetEffectDetailsDto(effectName)); } /// @@ -75,19 +76,7 @@ public Effect GetEffectDetails(string effectName) return null; } - return SendRequest(BaseUrl, Method.PUT, CreateWriteEffectCommand(effectName)); - } - - private static object CreateWriteEffectCommand(string effectName) - { - return new - { - write = new - { - command = "request", - animName = effectName - } - }; + return SendRequest(BaseUrl, Method.PUT, new GetEffectDetailsDto(effectName)); } } } diff --git a/Winleafs.Api/Endpoints/ExternalControlEndpoint.cs b/Winleafs.Api/Endpoints/ExternalControlEndpoint.cs index 08f0d43f..f5a7ae50 100644 --- a/Winleafs.Api/Endpoints/ExternalControlEndpoint.cs +++ b/Winleafs.Api/Endpoints/ExternalControlEndpoint.cs @@ -6,6 +6,9 @@ using System.Net; using System.Net.Sockets; using System.Threading.Tasks; +using Winleafs.Api.DTOs; +using Winleafs.Api.DTOs.ExternalControl; +using Winleafs.Api.DTOs.Shared; using Winleafs.Api.Endpoints.Interfaces; using Winleafs.Models.Enums; using Winleafs.Models.Models.ExternalControl; @@ -23,7 +26,7 @@ public class ExternalControlEndpoint : NanoleafEndpoint, IExternalControlEndpoin private static readonly byte _oneAsByte = Convert.ToByte(1); /// - public ExternalControlEndpoint(NanoleafClient client) + public ExternalControlEndpoint(ClientDto client) { Client = client; } @@ -36,7 +39,7 @@ public async Task GetExternalControlInfoAsync(DeviceType de return (deviceType) switch { DeviceType.Aurora => await SendRequestAsync(BaseUrl, Method.PUT, - body: new { write = new { command = "display", animType = "extControl" } }).ConfigureAwait(false), + body: new ExternalControlDto("v1")).ConfigureAwait(false), DeviceType.Canvas => await CreateExternalControlV2Body().ConfigureAwait(false), DeviceType.Shapes => await CreateExternalControlV2Body().ConfigureAwait(false), _ => throw new NotImplementedException($"No External Control exists for device of type {deviceType}") @@ -52,11 +55,11 @@ public async Task PrepareForExternalControl(DeviceType deviceType, string device switch (deviceType) { case DeviceType.Aurora: - _externalControlInfo = await GetExternalControlInfoAsync(deviceType); + _externalControlInfo = await GetExternalControlInfoAsync(deviceType).ConfigureAwait(false); break; case DeviceType.Canvas: case DeviceType.Shapes: - await GetExternalControlInfoAsync(deviceType); + await GetExternalControlInfoAsync(deviceType).ConfigureAwait(false); _externalControlInfo = new ExternalControlInfo { @@ -65,8 +68,6 @@ public async Task PrepareForExternalControl(DeviceType deviceType, string device StreamPort = CanvasShapesStreamPort }; break; - case DeviceType.Unknown: - break; default: throw new NotImplementedException($"No external control preparation implemented for device type {deviceType}"); } @@ -170,7 +171,7 @@ private static IEnumerable GetTwoBytesFromInteger(int value) private Task CreateExternalControlV2Body() { return SendRequestAsync(BaseUrl, Method.PUT, - body: new {write = new {command = "display", animType = "extControl", extControlVersion = "v2"}}); + body: new ExternalControlDto("v2")); } } } diff --git a/Winleafs.Api/Endpoints/IdentifyEndpoint.cs b/Winleafs.Api/Endpoints/IdentifyEndpoint.cs index 03e1c7b5..c5d5ad76 100644 --- a/Winleafs.Api/Endpoints/IdentifyEndpoint.cs +++ b/Winleafs.Api/Endpoints/IdentifyEndpoint.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using RestSharp; - +using Winleafs.Api.DTOs; using Winleafs.Api.Endpoints.Interfaces; namespace Winleafs.Api.Endpoints @@ -9,7 +9,7 @@ namespace Winleafs.Api.Endpoints public class IdentifyEndpoint : NanoleafEndpoint, IIdentifyEndpoint { /// - public IdentifyEndpoint(NanoleafClient client) + public IdentifyEndpoint(ClientDto client) { Client = client; } diff --git a/Winleafs.Api/Endpoints/LayoutEndpoint.cs b/Winleafs.Api/Endpoints/LayoutEndpoint.cs index fbae3979..e340f592 100644 --- a/Winleafs.Api/Endpoints/LayoutEndpoint.cs +++ b/Winleafs.Api/Endpoints/LayoutEndpoint.cs @@ -1,5 +1,6 @@ using RestSharp; using System.Threading.Tasks; +using Winleafs.Api.DTOs; using Winleafs.Api.Endpoints.Interfaces; using Winleafs.Models.Models.Layouts; @@ -10,7 +11,7 @@ public class LayoutEndpoint : NanoleafEndpoint, ILayoutEndpoint private const string BaseEndpoint = "panelLayout"; /// - public LayoutEndpoint(NanoleafClient client) + public LayoutEndpoint(ClientDto client) { Client = client; } diff --git a/Winleafs.Api/Endpoints/NanoleafEndpoint.cs b/Winleafs.Api/Endpoints/NanoleafEndpoint.cs index 95c95224..703e6a62 100644 --- a/Winleafs.Api/Endpoints/NanoleafEndpoint.cs +++ b/Winleafs.Api/Endpoints/NanoleafEndpoint.cs @@ -4,6 +4,8 @@ using Newtonsoft.Json; using NLog; using RestSharp; +using RestSharp.Serializers.NewtonsoftJson; +using Winleafs.Api.DTOs; namespace Winleafs.Api.Endpoints { @@ -14,7 +16,7 @@ public abstract class NanoleafEndpoint { private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - protected NanoleafClient Client { get; set; } + protected ClientDto Client { get; set; } // 2 Second default timeout. protected int Timeout { get; set; } = 2000; @@ -59,8 +61,9 @@ protected T SendRequest(string endpoint, Method method, object body = null) protected async Task SendRequestAsync(string endpoint, Method method, Type returnType = null, object body = null, bool disableLogging = false) { - var restClient = new RestClient(Client.BaseUri); - var request = new RestRequest(GetUrlForRequest(endpoint), method) + var restClient = new RestClient(Client.BaseUrl); + restClient.UseNewtonsoftJson(); + var request = new RestRequest(endpoint, method) { Timeout = Timeout }; @@ -96,8 +99,9 @@ protected async Task SendRequestAsync(string endpoint, Method method, /// The wanted result. protected object SendRequest(string endpoint, Method method, Type returnType = null, object body = null, bool disableLogging = false) { - var restClient = new RestClient(Client.BaseUri); - var request = new RestRequest(GetUrlForRequest(endpoint), method) + var restClient = new RestClient(Client.BaseUrl); + restClient.UseNewtonsoftJson(); + var request = new RestRequest(endpoint, method) { Timeout = Timeout //Set timeout to 2 seconds }; @@ -122,15 +126,11 @@ protected object SendRequest(string endpoint, Method method, Type returnType = n return returnType == null ? null : JsonConvert.DeserializeObject(response.Content, returnType); } - protected string GetUrlForRequest(string endpoint) - { - return $"api/v1/{Client.Token}/{endpoint}"; - } protected void LogRequest(RestRequest request, Method method, object body) { _logger.Info( - $"Sending following request to Nanoleaf: Address: {Client.BaseUri}, " + + $"Sending following request to Nanoleaf: Address: {Client.BaseUrl}, " + $"URL: {request.Resource}, Method: {method}, " + $"Body: {(body != null ? body.ToString() : "")}"); } diff --git a/Winleafs.Api/Endpoints/StateEndpoint.cs b/Winleafs.Api/Endpoints/StateEndpoint.cs index 89976656..96dec5a8 100644 --- a/Winleafs.Api/Endpoints/StateEndpoint.cs +++ b/Winleafs.Api/Endpoints/StateEndpoint.cs @@ -1,173 +1,166 @@ using System; using System.Threading.Tasks; - using RestSharp; - +using Winleafs.Api.DTOs; +using Winleafs.Api.DTOs.States; using Winleafs.Api.Endpoints.Interfaces; using Winleafs.Models.Models.State; namespace Winleafs.Api.Endpoints { - public class StateEndpoint : NanoleafEndpoint, IStateEndpoint - { - private const string BaseUrl = "state"; + public class StateEndpoint : NanoleafEndpoint, IStateEndpoint + { + private const string BaseUrl = "state"; - /// - public StateEndpoint(NanoleafClient client) - { - Client = client; - } + /// + public StateEndpoint(ClientDto client) + { + Client = client; + } - /// - public StateModel GetBrightness() + /// + public StateModel GetBrightness() { throw new NotImplementedException(); } - /// - public Task GetBrightnessAsync() - { - throw new NotImplementedException(); - } + /// + public Task GetBrightnessAsync() + { + throw new NotImplementedException(); + } - /// - public string GetColorMode() + /// + public string GetColorMode() { throw new NotImplementedException(); } - /// - public Task GetColorModeAsync() - { - throw new NotImplementedException(); - } + /// + public Task GetColorModeAsync() + { + throw new NotImplementedException(); + } - /// - public StateModel GetColorTemperature() + /// + public StateModel GetColorTemperature() { throw new NotImplementedException(); } - /// - public Task GetColorTemperatureAsync() - { - throw new NotImplementedException(); - } + /// + public Task GetColorTemperatureAsync() + { + throw new NotImplementedException(); + } - /// - public StateModel GetHue() + /// + public StateModel GetHue() { throw new NotImplementedException(); } - /// - public Task GetHueAsync() - { - throw new NotImplementedException(); - } + /// + public Task GetHueAsync() + { + throw new NotImplementedException(); + } - /// - public StateModel GetSaturation() + /// + public StateModel GetSaturation() { throw new NotImplementedException(); } - /// - public Task GetSaturationAsync() - { - throw new NotImplementedException(); - } + /// + public Task GetSaturationAsync() + { + throw new NotImplementedException(); + } - /// - public OnOffModel GetState() + /// + public OnOffModel GetState() { return GetStateAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } - /// - public async Task GetStateAsync() - { - return await SendRequestAsync($"{BaseUrl}/on", Method.GET); - } + /// + public async Task GetStateAsync() + { + return await SendRequestAsync($"{BaseUrl}/on", Method.GET); + } - /// - public void IncrementBrightness(int increment) + /// + public void IncrementBrightness(int increment) { throw new NotImplementedException(); } - /// - public Task IncrementBrightnessAsync(int increment) - { - throw new NotImplementedException(); - } + /// + public Task IncrementBrightnessAsync(int increment) + { + throw new NotImplementedException(); + } - /// - public Task IncrementColorTemperatureAsync(int increment) - { - throw new NotImplementedException(); - } + /// + public Task IncrementColorTemperatureAsync(int increment) + { + throw new NotImplementedException(); + } - /// - public void IncrementColorTemperature(int increment) + /// + public void IncrementColorTemperature(int increment) { throw new NotImplementedException(); } - /// - public void IncrementHue(int increment) + /// + public void IncrementHue(int increment) { throw new NotImplementedException(); } - /// - public Task IncrementHueAsync(int increment) - { - throw new NotImplementedException(); - } + /// + public Task IncrementHueAsync(int increment) + { + throw new NotImplementedException(); + } - /// - public void IncrementSaturation(int increment) + /// + public void IncrementSaturation(int increment) { throw new NotImplementedException(); } - /// - public Task IncrementSaturationAsync(int increment) - { - throw new NotImplementedException(); - } + /// + public Task IncrementSaturationAsync(int increment) + { + throw new NotImplementedException(); + } - /// - public void SetBrightness(int value, int? duration = null) + /// + public void SetBrightness(int value, int? duration = null) { SetBrightnessAsync(value, duration).ConfigureAwait(false).GetAwaiter().GetResult(); } - /// - public Task SetBrightnessAsync(int value, int? duration = null) - { - if (duration.HasValue) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new { brightness = new {value, duration}}); - } - else - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new { brightness = new { value }}); - } - } + /// + public Task SetBrightnessAsync(int value, int? duration = null) + { + return SendRequestAsync(BaseUrl, Method.PUT, body: SetStateValuesDto.SetBrightness(value, duration)); + } - /// - public void SetColorTemperature(int value) + /// + public void SetColorTemperature(int value) { - SetColorTemperatureAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); + SetColorTemperatureAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); } - /// - public Task SetColorTemperatureAsync(int value) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new {ct = new {value}}); - } + /// + public Task SetColorTemperatureAsync(int value) + { + return SendRequestAsync(BaseUrl, Method.PUT, body: new {ct = new {value}}); + } public void SetHue(int value) { @@ -175,19 +168,19 @@ public void SetHue(int value) } public Task SetHueAsync(int value) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new {hue = new {value}}); - } + { + return SendRequestAsync(BaseUrl, Method.PUT, body: SetStateValuesDto.SetHue(value)); + } public void SetSaturation(int value) { - SetSaturationAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); + SetSaturationAsync(value).ConfigureAwait(false).GetAwaiter().GetResult(); } public Task SetSaturationAsync(int value) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new { sat = new { value }}); - } + { + return SendRequestAsync(BaseUrl, Method.PUT, body: SetStateValuesDto.SetSaturation(value)); + } public void SetState(bool state) { @@ -195,9 +188,9 @@ public void SetState(bool state) } public Task SetStateAsync(bool state) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new {on = new {value = state}}); - } + { + return SendRequestAsync(BaseUrl, Method.PUT, body: new SetStateDto(state)); + } public void SetStateWithStateCheck(bool state) { @@ -205,42 +198,33 @@ public void SetStateWithStateCheck(bool state) } public async Task SetStateWithStateCheckAsync(bool state) - { - var currentState = (await GetStateAsync())?.IsTurnedOn; - - if (currentState != state) - { - await SetStateAsync(state); - } - } - - public void SetHueAndSaturation(int hue, int saturation) - { - SetHueAndSaturationAsync(hue, saturation).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Task SetHueAndSaturationAsync(int hue, int saturation, bool disableLogging = false) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new - { - sat = new {value = saturation}, - hue = new {value = hue} - }, disableLogging: disableLogging); - } - - public void SetHueSaturationAndBrightness(int hue, int saturation, int brightness) - { - SetHueSaturationAndBrightnessAsync(hue, saturation, brightness).ConfigureAwait(false).GetAwaiter().GetResult(); - } - - public Task SetHueSaturationAndBrightnessAsync(int hue, int saturation, int brightness, bool disableLogging = false) - { - return SendRequestAsync(BaseUrl, Method.PUT, body: new - { - sat = new { value = saturation }, - hue = new { value = hue}, - brightness = new { value = brightness } - }, disableLogging: disableLogging); - } - } + { + var currentState = (await GetStateAsync())?.IsTurnedOn; + + if (currentState != state) + { + await SetStateAsync(state); + } + } + + public void SetHueAndSaturation(int hue, int saturation) + { + SetHueAndSaturationAsync(hue, saturation).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public Task SetHueAndSaturationAsync(int hue, int saturation, bool disableLogging = false) + { + return SendRequestAsync(BaseUrl, Method.PUT, body: new SetStateValuesDto(hue, saturation), disableLogging: disableLogging); + } + + public void SetHueSaturationAndBrightness(int hue, int saturation, int brightness) + { + SetHueSaturationAndBrightnessAsync(hue, saturation, brightness).ConfigureAwait(false).GetAwaiter().GetResult(); + } + + public Task SetHueSaturationAndBrightnessAsync(int hue, int saturation, int brightness, bool disableLogging = false) + { + return SendRequestAsync(BaseUrl, Method.PUT, body: new SetStateValuesDto(hue, saturation, brightness), disableLogging: disableLogging); + } + } } diff --git a/Winleafs.Api/Exceptions/InvalidDeviceException.cs b/Winleafs.Api/Exceptions/InvalidDeviceException.cs new file mode 100644 index 00000000..81f8c978 --- /dev/null +++ b/Winleafs.Api/Exceptions/InvalidDeviceException.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.Serialization; + +namespace Winleafs.Api.Exceptions +{ + [Serializable] + public class InvalidDeviceException : Exception + { + public InvalidDeviceException() + { + } + + public InvalidDeviceException(string message) : base(message) + { + } + + public InvalidDeviceException(string message, Exception innerException) : base(message, innerException) + { + } + + protected InvalidDeviceException(SerializationInfo info, StreamingContext context) : base(info, context) + { + } + } +} diff --git a/Winleafs.Api/NanoleafClient.cs b/Winleafs.Api/NanoleafClient.cs index 8a01daf4..f96e3e22 100644 --- a/Winleafs.Api/NanoleafClient.cs +++ b/Winleafs.Api/NanoleafClient.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; - +using Winleafs.Api.DTOs; using Winleafs.Api.Endpoints; using Winleafs.Api.Endpoints.Interfaces; -using Winleafs.Models.Models; namespace Winleafs.Api { @@ -20,66 +17,30 @@ public interface INanoleafClient ILayoutEndpoint LayoutEndpoint { get; } IExternalControlEndpoint ExternalControlEndpoint { get; } - } public class NanoleafClient : INanoleafClient { - private static readonly Dictionary _clients = new Dictionary(); - - internal Uri BaseUri; - - internal string Token; - - /// - /// Initializes a new instance of the class. - /// - /// The IP address at which the Nanoleaf device lives. - /// The port that is used to access the device. - /// The access token to access the device's API. - public NanoleafClient(string ip, int port, string token = null) + public NanoleafClient(ClientDto client) { - BaseUri = new Uri($"http://{ip}:{port}"); - Token = token; + EffectsEndpoint = new EffectsEndpoint(client); + AuthorizationEndpoint = new AuthorizationEndpoint(client); + StateEndpoint = new StateEndpoint(client); + LayoutEndpoint = new LayoutEndpoint(client); + IdentifyEndpoint = new IdentifyEndpoint(client); + ExternalControlEndpoint = new ExternalControlEndpoint(client); } - /// - /// Gets a for the given . - /// - /// The device wanting the for. - /// An instance of a class inheriting . - public static INanoleafClient GetClientForDevice(Device device) - { - if (!_clients.ContainsKey(device.IPAddress)) - { - _clients.Add(device.IPAddress, new NanoleafClient(device.IPAddress, device.Port, device.AuthToken)); - } - - return _clients[device.IPAddress]; - } - - private IEffectsEndpoint _effectsEndpoint; - - public IEffectsEndpoint EffectsEndpoint => _effectsEndpoint ??= new EffectsEndpoint(this); - - private IAuthorizationEndpoint _authorizationEndpoint; - - public IAuthorizationEndpoint AuthorizationEndpoint => _authorizationEndpoint ??= new AuthorizationEndpoint(this); - - private IStateEndpoint _stateEndpoint; - - public IStateEndpoint StateEndpoint => _stateEndpoint ??= new StateEndpoint(this); - - private ILayoutEndpoint _layoutEndpoint; + public IEffectsEndpoint EffectsEndpoint { get; } - public ILayoutEndpoint LayoutEndpoint => _layoutEndpoint ??= new LayoutEndpoint(this); + public IAuthorizationEndpoint AuthorizationEndpoint { get; } - private IIdentifyEndpoint _identifyEndpoint; + public IStateEndpoint StateEndpoint { get; } - public IIdentifyEndpoint IdentifyEndpoint => _identifyEndpoint ??= new IdentifyEndpoint(this); + public ILayoutEndpoint LayoutEndpoint { get; } - private IExternalControlEndpoint _externalControlEndpoint; + public IIdentifyEndpoint IdentifyEndpoint { get; } - public IExternalControlEndpoint ExternalControlEndpoint => _externalControlEndpoint ??= new ExternalControlEndpoint(this); + public IExternalControlEndpoint ExternalControlEndpoint { get; } } } diff --git a/Winleafs.Api/Winleafs.Api.csproj b/Winleafs.Api/Winleafs.Api.csproj index 5ec76c9f..15662ef2 100644 --- a/Winleafs.Api/Winleafs.Api.csproj +++ b/Winleafs.Api/Winleafs.Api.csproj @@ -12,6 +12,7 @@ + diff --git a/Winleafs.Wpf/Api/Effects/CustomEffectsCollection.cs b/Winleafs.Wpf/Api/Effects/CustomEffectsCollection.cs index 20ea3bff..7aaf83e7 100644 --- a/Winleafs.Wpf/Api/Effects/CustomEffectsCollection.cs +++ b/Winleafs.Wpf/Api/Effects/CustomEffectsCollection.cs @@ -16,7 +16,7 @@ public class CustomEffectsCollection /// The orchestrator instance currently in use. public CustomEffectsCollection(Orchestrator orchestrator) { - var nanoleafClient = NanoleafClient.GetClientForDevice(orchestrator.Device); + var nanoleafClient = ClientFactory.Instance.Get(orchestrator.Device); _customEffects = new Dictionary(); diff --git a/Winleafs.Wpf/Api/Effects/ScreenMirrorEffect.cs b/Winleafs.Wpf/Api/Effects/ScreenMirrorEffect.cs index 2dc34063..7db94684 100644 --- a/Winleafs.Wpf/Api/Effects/ScreenMirrorEffect.cs +++ b/Winleafs.Wpf/Api/Effects/ScreenMirrorEffect.cs @@ -44,7 +44,7 @@ public ScreenMirrorEffect(Orchestrator orchestrator, INanoleafClient nanoleafCli } else if (_screenMirrorAlgorithm == ScreenMirrorAlgorithm.Ambilight) { - _screenMirrorEffect = new Ambilght(nanoleafClient); + _screenMirrorEffect = new Ambilight(nanoleafClient); } } catch (Exception e) diff --git a/Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/Ambilght.cs b/Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/Ambilight.cs similarity index 96% rename from Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/Ambilght.cs rename to Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/Ambilight.cs index 0298a354..9466b86c 100644 --- a/Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/Ambilght.cs +++ b/Winleafs.Wpf/Api/Effects/ScreenMirrorEffects/Ambilight.cs @@ -7,12 +7,12 @@ namespace Winleafs.Wpf.Api.Effects.ScreenMirrorEffects { - public class Ambilght : IScreenMirrorEffect + public class Ambilight : IScreenMirrorEffect { private readonly INanoleafClient _nanoleafClient; private readonly List _captureArea; //A list since that is what the screengrabber expects as input - public Ambilght(INanoleafClient nanoleafClient) + public Ambilight(INanoleafClient nanoleafClient) { _nanoleafClient = nanoleafClient; diff --git a/Winleafs.Wpf/Api/Layouts/PanelLayout.cs b/Winleafs.Wpf/Api/Layouts/PanelLayout.cs index 3e3b8447..f3f9bf19 100644 --- a/Winleafs.Wpf/Api/Layouts/PanelLayout.cs +++ b/Winleafs.Wpf/Api/Layouts/PanelLayout.cs @@ -30,7 +30,7 @@ public class PanelLayout public PanelLayout(Device device) { - _nanoleafClient = NanoleafClient.GetClientForDevice(device); + _nanoleafClient = ClientFactory.Instance.Get(device); _unscaledPolygons = new List(); diff --git a/Winleafs.Wpf/Api/Orchestrator.cs b/Winleafs.Wpf/Api/Orchestrator.cs index b20357a8..447eb429 100644 --- a/Winleafs.Wpf/Api/Orchestrator.cs +++ b/Winleafs.Wpf/Api/Orchestrator.cs @@ -92,7 +92,7 @@ public async Task ActivateEffect(string effectName, int brightness) try { - var client = NanoleafClient.GetClientForDevice(Device); + var client = ClientFactory.Instance.Get(Device); //DO NOT change the order of disabling effects, then setting brightness and then enabling effects if (_customEffects.HasActiveEffects(effectName)) diff --git a/Winleafs.Wpf/Api/ScheduleTimer.cs b/Winleafs.Wpf/Api/ScheduleTimer.cs index 06b94db0..e55f0908 100644 --- a/Winleafs.Wpf/Api/ScheduleTimer.cs +++ b/Winleafs.Wpf/Api/ScheduleTimer.cs @@ -74,7 +74,7 @@ private async Task SetEffectsForDevices() { _logger.Info($"Scheduler turning device {_device.IPAddress} off"); - var client = NanoleafClient.GetClientForDevice(_device); + var client = ClientFactory.Instance.Get(_device); //There are no triggers so the lights can be turned off if it is not off already await client.StateEndpoint.SetStateWithStateCheckAsync(false); diff --git a/Winleafs.Wpf/Views/App.xaml.cs b/Winleafs.Wpf/Views/App.xaml.cs index d156a1e1..63fd21a9 100644 --- a/Winleafs.Wpf/Views/App.xaml.cs +++ b/Winleafs.Wpf/Views/App.xaml.cs @@ -161,7 +161,7 @@ private async Task TurnOffLights() UserSettings.Settings.ActiveSchedule.AppliesToDeviceNames.Contains(device.Name) && UserSettings.Settings.ActiveSchedule.TurnOffAtApplicationShutdown) { - var client = NanoleafClient.GetClientForDevice(device); + var client = ClientFactory.Instance.Get(device); await client.StateEndpoint.SetStateWithStateCheckAsync(false); } } diff --git a/Winleafs.Wpf/Views/Effects/EffectComboBox.xaml.cs b/Winleafs.Wpf/Views/Effects/EffectComboBox.xaml.cs index 53a17244..bc802708 100644 --- a/Winleafs.Wpf/Views/Effects/EffectComboBox.xaml.cs +++ b/Winleafs.Wpf/Views/Effects/EffectComboBox.xaml.cs @@ -80,7 +80,7 @@ private void BuildEffects(IEnumerable customEffects, IEnumerable< var effects = new List(); //Take any client. Since the dropdown will only display effects shared accross all devices, it does not matter which lights we use to query the effects - var nanoleafClient = NanoleafClient.GetClientForDevice(orchestrators.First().Device); + var nanoleafClient = ClientFactory.Instance.Get(orchestrators.First().Device); foreach (var customEffect in customEffects) { diff --git a/Winleafs.Wpf/Views/Layout/LayoutDisplayUserControl.xaml.cs b/Winleafs.Wpf/Views/Layout/LayoutDisplayUserControl.xaml.cs index c0bdfba0..f1ebf4cb 100644 --- a/Winleafs.Wpf/Views/Layout/LayoutDisplayUserControl.xaml.cs +++ b/Winleafs.Wpf/Views/Layout/LayoutDisplayUserControl.xaml.cs @@ -171,7 +171,7 @@ public void UpdateColors() //Only retrieve palette if it is not known yet if (effect?.Palette == null) { - var client = NanoleafClient.GetClientForDevice(UserSettings.Settings.ActiveDevice); + var client = ClientFactory.Instance.Get(UserSettings.Settings.ActiveDevice); effect = client.EffectsEndpoint.GetEffectDetails(effectName); if (effect != null) diff --git a/Winleafs.Wpf/Views/MainWindows/MainWindow.xaml.cs b/Winleafs.Wpf/Views/MainWindows/MainWindow.xaml.cs index 660bbe2c..f2e71fa3 100644 --- a/Winleafs.Wpf/Views/MainWindows/MainWindow.xaml.cs +++ b/Winleafs.Wpf/Views/MainWindows/MainWindow.xaml.cs @@ -290,7 +290,7 @@ private async void Reload_Click(object sender, RoutedEventArgs e) { foreach (var device in UserSettings.Settings.Devices) { - var nanoleafClient = NanoleafClient.GetClientForDevice(device); + var nanoleafClient = ClientFactory.Instance.Get(device); var effects = await nanoleafClient.EffectsEndpoint.GetEffectsListAsync(); var orchestrator = OrchestratorCollection.GetOrchestratorForDevice(device); diff --git a/Winleafs.Wpf/Views/Setup/SetupWindow.xaml.cs b/Winleafs.Wpf/Views/Setup/SetupWindow.xaml.cs index dfdf288e..8dbc8469 100644 --- a/Winleafs.Wpf/Views/Setup/SetupWindow.xaml.cs +++ b/Winleafs.Wpf/Views/Setup/SetupWindow.xaml.cs @@ -11,6 +11,7 @@ using Winleafs.Wpf.ViewModels; using NLog; +using Winleafs.Api.DTOs; namespace Winleafs.Wpf.Views.Setup { @@ -28,7 +29,7 @@ public partial class SetupWindow : Window private SetupViewModel setupViewModel; private List discoveredDevices; - private NanoleafClient nanoleafClient; + private INanoleafClient nanoleafClient; private Device selectedDevice; private MainWindow _parent; @@ -161,7 +162,7 @@ public void Continue_Click(object sender, RoutedEventArgs e) selectedDevice = (Device)DiscoverDevice.Devices.SelectedItem; - nanoleafClient = new NanoleafClient(selectedDevice.IPAddress, selectedDevice.Port); + nanoleafClient = ClientFactory.Instance.Get(selectedDevice); } }