diff --git a/WifiPlug.API.sln b/WifiPlug.API.sln index 3319052..f941467 100644 --- a/WifiPlug.API.sln +++ b/WifiPlug.API.sln @@ -16,6 +16,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.md = README.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.EventCli", "samples\Example.EventCli\Example.EventCli.csproj", "{B2323C21-8EB7-4683-96EC-E3D699C391E9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -34,6 +36,10 @@ Global {46209885-F892-42A7-A857-82FD7EBF178A}.Debug|Any CPU.Build.0 = Debug|Any CPU {46209885-F892-42A7-A857-82FD7EBF178A}.Release|Any CPU.ActiveCfg = Release|Any CPU {46209885-F892-42A7-A857-82FD7EBF178A}.Release|Any CPU.Build.0 = Release|Any CPU + {B2323C21-8EB7-4683-96EC-E3D699C391E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2323C21-8EB7-4683-96EC-E3D699C391E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2323C21-8EB7-4683-96EC-E3D699C391E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2323C21-8EB7-4683-96EC-E3D699C391E9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -41,6 +47,7 @@ Global GlobalSection(NestedProjects) = preSolution {3A580E5E-A795-42D9-A3B1-DCAA9C7807C7} = {6873EB4E-6281-4CB1-8C41-05F2A1AF1EDA} {46209885-F892-42A7-A857-82FD7EBF178A} = {6873EB4E-6281-4CB1-8C41-05F2A1AF1EDA} + {B2323C21-8EB7-4683-96EC-E3D699C391E9} = {6873EB4E-6281-4CB1-8C41-05F2A1AF1EDA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8234EEE7-61B4-403F-8C60-65A019D95FEA} diff --git a/samples/Example.EventCli/Example.EventCli.csproj b/samples/Example.EventCli/Example.EventCli.csproj new file mode 100644 index 0000000..196fcf9 --- /dev/null +++ b/samples/Example.EventCli/Example.EventCli.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.1 + + + + + + + diff --git a/samples/Example.EventCli/Program.cs b/samples/Example.EventCli/Program.cs new file mode 100644 index 0000000..9646cec --- /dev/null +++ b/samples/Example.EventCli/Program.cs @@ -0,0 +1,13 @@ +using System; +using WifiPlug.Api; + +namespace Example.EventCli +{ + class Program + { + static void Main(string[] args) + { + //EventClient eventClient = new EventClient("ws://localhost", "devkey", "devsecret"); + } + } +} diff --git a/src/WifiPlug.Api/ApiClient.cs b/src/WifiPlug.Api/ApiClient.cs index 8ae8299..fe29e6f 100644 --- a/src/WifiPlug.Api/ApiClient.cs +++ b/src/WifiPlug.Api/ApiClient.cs @@ -20,7 +20,7 @@ namespace WifiPlug.Api public class ApiClient : IApiClient { #region Constants - internal const string API_URL = "https://api.wifiplug.co.uk/v1.0/"; + internal const string ApiUrl = "https://api.wifiplug.co.uk/v1.0/"; #endregion #region Fields @@ -428,7 +428,7 @@ public ApiClient() : this(null) { } /// /// The API key. /// The API secret. - public ApiClient(string apiKey, string apiSecret) : this(API_URL) { + public ApiClient(string apiKey, string apiSecret) : this(ApiUrl) { if (apiKey == null) throw new ArgumentNullException(nameof(apiKey)); else if (apiSecret == null) @@ -450,7 +450,7 @@ public ApiClient(string apiKey, string apiSecret) : this(API_URL) { public ApiClient(string apiUrl) { // setup client _client = new HttpClient(); - _client.BaseAddress = new Uri(apiUrl == null ? API_URL : apiUrl); + _client.BaseAddress = new Uri(apiUrl == null ? ApiUrl : apiUrl); _client.DefaultRequestHeaders.Add("X-API-Client", "api-client-net/1.0"); // initialize operations diff --git a/src/WifiPlug.Api/Authentication/OAuth2Authentication.cs b/src/WifiPlug.Api/Authentication/OAuth2Authentication.cs new file mode 100644 index 0000000..1b63e66 --- /dev/null +++ b/src/WifiPlug.Api/Authentication/OAuth2Authentication.cs @@ -0,0 +1,119 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace WifiPlug.Api.Authentication +{ + /// + /// Provides full OAuth2 authentication. + /// + sealed class OAuth2Authentication : BearerAuthentication + { + #region Constants + internal const string TokenUrl = "https://account.wifiplug.co.uk/oauth2/token"; + #endregion + + #region Fields + private Uri _tokenUri; + private string _refreshToken; + #endregion + + #region Methods + /// + /// Applies bearer authentication to an outgoing request. + /// + /// The request. + public override void Apply(HttpRequestMessage request) { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _bearerToken); + } + + /// + /// Deserializes the bearer authentication from a stream. + /// + /// The stream. + public override void Deserialize(Stream stream) { + // deserialize bearer token + base.Deserialize(stream); + + // read refresh token + BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, true); + + + } + + /// + /// Serializes the bearer authentication to a stream. + /// + /// The stream. + public override void Serialize(Stream stream) { + // serialize bearer token + base.Serialize(stream); + + // write refresh token + BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true); + } + + /// + /// Refreshes the token. + /// + /// The client. + /// + public override Task ReauthorizeAsync(ApiClient client) { + // create refresh client + HttpClient refreshClient = new HttpClient(); + refreshClient.BaseAddress = _tokenUri; + + // make request + //refreshClient.PostAsync("/") + + return Task.FromResult(false); + } + #endregion + + #region Entities + class RefreshAccessTokenEntity + { + [JsonProperty("grant_type")] + public string GrantType { get; } = "refresh_token"; + + [JsonProperty("refresh_token")] + public string RefreshToken { get; set; } + } + #endregion + + #region Constructors + /// + /// Creates an empty oAuth2 authentication object. + /// + public OAuth2Authentication() + : base() { + } + + /// + /// Creates a new oAuth2 authentication object from an access and refresh token. + /// + /// The access token. + /// The refresh token. + public OAuth2Authentication(string accessToken, string refreshToken) + : this(null, accessToken, refreshToken) { + _refreshToken = refreshToken; + } + + /// + /// Creates a new oAuth2 authentication object from an access and refresh token. + /// + /// The optional token URI. + /// The access token. + /// The refresh token. + public OAuth2Authentication(string tokenUri, string accessToken, string refreshToken) { + _tokenUri = tokenUri == null ? new Uri(TokenUrl) : new Uri(tokenUri); + _refreshToken = refreshToken; + } + #endregion + } +} diff --git a/src/WifiPlug.Api/Entities/DeviceAddEntity.cs b/src/WifiPlug.Api/Entities/DeviceAddEntity.cs new file mode 100644 index 0000000..cf92518 --- /dev/null +++ b/src/WifiPlug.Api/Entities/DeviceAddEntity.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace WifiPlug.Api.Entities +{ + /// + /// Represents a request to add a device, this is a legacy request and not supported by most API keys. + /// + public class DeviceAddEntity + { + /// + /// Gets or sets the name. + /// + [JsonProperty(PropertyName = "name")] + public string Name { get; set; } + + /// + /// Gets or sets the MAC address. + /// + [JsonProperty(PropertyName = "mac_address")] + public string MacAddress { get; set; } + + /// + /// Gets or sets the type code. + /// + [JsonProperty(PropertyName = "type_code")] + public int TypeCode { get; set; } + + /// + /// Gets or sets the variant. + /// + [JsonProperty(PropertyName = "variant")] + public string Variant { get; set; } + + /// + /// Gets or sets the firmware version. + /// + [JsonProperty(PropertyName = "firmware_version")] + public string FirmwareVersion { get; set; } + } +} diff --git a/src/WifiPlug.Api/Entities/GroupTimerAddEntity.cs b/src/WifiPlug.Api/Entities/GroupTimerAddEntity.cs index 9f2d752..c64255c 100644 --- a/src/WifiPlug.Api/Entities/GroupTimerAddEntity.cs +++ b/src/WifiPlug.Api/Entities/GroupTimerAddEntity.cs @@ -22,6 +22,7 @@ public class GroupTimerAddEntity /// Gets or sets the days the timer will repeat on, if any. /// [JsonProperty(PropertyName = "repeats", DefaultValueHandling = DefaultValueHandling.Ignore)] + [JsonConverter(typeof(TimerRepetitionConverter))] public TimerRepetition Repeats { get; set; } /// diff --git a/src/WifiPlug.Api/Event.cs b/src/WifiPlug.Api/Event.cs new file mode 100644 index 0000000..dd28f5d --- /dev/null +++ b/src/WifiPlug.Api/Event.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Text; + +namespace WifiPlug.Api +{ + /// + /// Represents a received event. + /// + class Event + { + #region Properties + public string Name { get; private set; } + public string ResourceType { get; private set; } + public string Resource { get; private set; } + public JObject Payload { get; private set; } + #endregion + + #region Constructors + + #endregion + } +} diff --git a/src/WifiPlug.Api/EventClient.cs b/src/WifiPlug.Api/EventClient.cs new file mode 100644 index 0000000..f4599cc --- /dev/null +++ b/src/WifiPlug.Api/EventClient.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace WifiPlug.Api +{ + /// + /// Provides access to the WIFIPLUG event subscription API. + /// + class EventClient : IObservable + { + #region Constants + internal const string API_URL = "wss://event.wifiplug.co.uk/v1.0"; + #endregion + + #region Fields + private string _apiKey = null; + private string _apiSecret = null; + private ClientWebSocket _client = null; + private Uri _uri = null; + #endregion + + #region Methods + /// + /// Subscribes to the provided selector. + /// + /// The subscription selector. + /// The cancellation token. + /// await SubscribeAsync("device/*/*); + /// + public async Task SubscribeAsync(string selector, CancellationToken cancellationToken = default(CancellationToken)) { + // connect if not open + if (_client.State != WebSocketState.Open) + await _client.ConnectAsync(_uri, cancellationToken).ConfigureAwait(false); + + // throw if cancelled + cancellationToken.ThrowIfCancellationRequested(); + + // subscribe + //_client.SendAsync(new ArraySegment()); + } + + /// + /// Unsubscribes the provided selector, you can only unsubscribe the exact selector you subscribed previously. + /// + /// The selector. + /// The cancellation token. + /// + public async Task UnsubscribeAsync(string selector, CancellationToken cancellationToken = default(CancellationToken)) { + // connect if not open + if (_client.State != WebSocketState.Open) + await _client.ConnectAsync(_uri, cancellationToken).ConfigureAwait(false); + + // throw if cancelled + cancellationToken.ThrowIfCancellationRequested(); + + // unsubscribe + } + + /// + /// Closes the underlying event streaming client gracefully. + /// + /// The cancellation token. + /// + public Task CloseAsync(CancellationToken cancellationToken = default(CancellationToken)) { + return _client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client requested closure", cancellationToken); + } + #endregion + + public IDisposable Subscribe(IObserver observer) { + throw new NotImplementedException(); + } + + #region Constructors + /// + /// Creates a new event client and configures the provided event URL and api credentials. + /// + /// Your API credentials must allow event streaming. + /// The API key. + /// The API secret. + public EventClient(string apiKey, string apiSecret) + : this(null, apiKey, apiSecret){ + } + + /// + /// Creates a new event client and configures the provided event URL and api credentials. + /// + /// Your API credentials must allow event streaming. + /// The event URL. + /// The API key. + /// The API secret. + public EventClient(string eventUrl, string apiKey, string apiSecret) { + _client = new ClientWebSocket(); + } + #endregion + } +} \ No newline at end of file diff --git a/src/WifiPlug.Api/Operations/DeviceOperations.cs b/src/WifiPlug.Api/Operations/DeviceOperations.cs index b3bb2bb..74f17ae 100644 --- a/src/WifiPlug.Api/Operations/DeviceOperations.cs +++ b/src/WifiPlug.Api/Operations/DeviceOperations.cs @@ -23,6 +23,17 @@ public class DeviceOperations : IDeviceOperations /// protected ApiClient _client; + /// + /// Adds a device. + /// + /// The group entity. + /// The cancellation token. + /// This operation is internal and won't work with normal API keys. Nor is it stable. + /// The added device. + public Task AddDeviceAsync(DeviceAddEntity entity, CancellationToken cancellationToken = default(CancellationToken)) { + return _client.RequestJsonSerializedAsync(HttpMethod.Post, "device/add", entity, cancellationToken); + } + /// /// Gets a live energy reading from the device service, if applicable. /// diff --git a/src/WifiPlug.Api/Operations/IDeviceOperations.cs b/src/WifiPlug.Api/Operations/IDeviceOperations.cs index e6ccf75..bfb16e6 100644 --- a/src/WifiPlug.Api/Operations/IDeviceOperations.cs +++ b/src/WifiPlug.Api/Operations/IDeviceOperations.cs @@ -13,6 +13,15 @@ namespace WifiPlug.Api.Operations /// public interface IDeviceOperations { + /// + /// Adds a device. + /// + /// The group entity. + /// The cancellation token. + /// This operation is internal and won't work with normal API keys. Nor is it stable. + /// The added device. + Task AddDeviceAsync(DeviceAddEntity entity, CancellationToken cancellationToken = default(CancellationToken)); + /// /// Gets a live energy reading from the device service, if applicable. /// diff --git a/src/WifiPlug.Api/WifiPlug.Api.csproj b/src/WifiPlug.Api/WifiPlug.Api.csproj index 723c2ad..e077cf8 100644 --- a/src/WifiPlug.Api/WifiPlug.Api.csproj +++ b/src/WifiPlug.Api/WifiPlug.Api.csproj @@ -11,12 +11,12 @@ https://wifiplug.co.uk https://github.com/wifiplug/api-client-net git - 1.0.9.0 - 1.0.9.0 + 1.0.10.0 + 1.0.10.0 true https://s3.eu-west-2.amazonaws.com/wifiplug-pub/nuget-icons/wifiplug.png https://github.com/wifiplug/api-client-net/blob/master/LICENSE - 1.0.9 + 1.0.10 @@ -29,6 +29,7 @@ +