Rewrite Hierachy that shockers now contain the api they use.

This commit is contained in:
2024-02-01 23:03:28 +01:00
parent 9596811ae7
commit 65059f66ed
18 changed files with 171 additions and 168 deletions

View File

@ -7,7 +7,7 @@
<Authors>Glax</Authors>
<RepositoryUrl>https://github.com/C9Glax/CShocker</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>2.0.2</Version>
<Version>2.1.0</Version>
</PropertyGroup>
<ItemGroup>

View File

@ -7,13 +7,13 @@ using CShocker.Shockers;
using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices;
namespace CShocker.Devices.APIs;
public class OpenShockHttp : OpenShockDevice
public class OpenShockHttp : OpenShockApi
{
protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
protected override void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration)
{
if (shocker is not OpenShockShocker openShockShocker)
{

View File

@ -6,9 +6,9 @@ using CShocker.Shockers;
using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices;
namespace CShocker.Devices.APIs;
public class OpenShockSerial : OpenShockDevice
public class OpenShockSerial : OpenShockApi
{
private const int BaudRate = 115200;
public SerialPortInfo SerialPortI;
@ -18,10 +18,18 @@ public class OpenShockSerial : OpenShockDevice
{
this.SerialPortI = serialPortI;
this._serialPort = new SerialPort(serialPortI.PortName, BaudRate);
this._serialPort.Open();
try
{
this._serialPort.Open();
}
catch (Exception e)
{
this.Logger?.Log(LogLevel.Error, e.Message);
throw;
}
}
protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
protected override void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration)
{
if (shocker is not OpenShockShocker openShockShocker)
{

View File

@ -7,9 +7,9 @@ using CShocker.Shockers;
using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices;
namespace CShocker.Devices.APIs;
public class PiShockHttp : PiShockDevice
public class PiShockHttp : PiShockApi
{
// ReSharper disable twice MemberCanBePrivate.Global external usage
public string Username, Endpoint, ApiKey;
@ -22,7 +22,7 @@ public class PiShockHttp : PiShockDevice
this.ApiKey = apiKey;
}
protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
protected override void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration)
{
if (shocker is not PiShockShocker piShockShocker)
{

View File

@ -5,9 +5,9 @@ using CShocker.Ranges;
using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices;
namespace CShocker.Devices.APIs;
public class PiShockSerial : PiShockDevice
public class PiShockSerial : PiShockApi
{
private const int BaudRate = 115200;
public SerialPortInfo SerialPortI;
@ -20,7 +20,7 @@ public class PiShockSerial : PiShockDevice
throw new NotImplementedException();
}
protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
protected override void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration)
{
string json = "{" +
"\"cmd\": \"operate\"," +

View File

@ -5,20 +5,20 @@ using Microsoft.Extensions.Logging;
namespace CShocker.Devices.Abstract;
public abstract class Device : IDisposable
public abstract class Api : IDisposable
{
// ReSharper disable 4 times MemberCanBePrivate.Global external use
public readonly IntensityRange IntensityRange;
public readonly DurationRange DurationRange;
protected ILogger? Logger;
public readonly DeviceApi ApiType;
private readonly Queue<ValueTuple<ControlAction, IShocker, int, int>> _queue = new();
private readonly Queue<ValueTuple<ControlAction, Shocker, int, int>> _queue = new();
private bool _workQueue = true;
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly Thread _workQueueThread;
private const short CommandDelay = 50;
public void Control(ControlAction action, int? intensity = null, int? duration = null, params IShocker[] shockers)
internal void Control(ControlAction action, int? intensity = null, int? duration = null, params Shocker[] shockers)
{
int i = intensity ?? IntensityRange.GetRandomRangeValue();
int d = duration ?? DurationRange.GetRandomRangeValue();
@ -27,16 +27,16 @@ public abstract class Device : IDisposable
this.Logger?.Log(LogLevel.Information, "Doing nothing");
return;
}
foreach (IShocker shocker in shockers)
foreach (Shocker shocker in shockers)
{
this.Logger?.Log(LogLevel.Debug, $"Enqueueing {action} {(intensity is not null ? $"Overwrite {i}" : $"{i}")} {(duration is not null ? $"Overwrite {d}" : $"{d}")}");
_queue.Enqueue(new(action, shocker, i ,d));
}
}
protected abstract void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration);
protected abstract void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration);
protected Device(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, ILogger? logger = null)
protected Api(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, ILogger? logger = null)
{
this.IntensityRange = intensityRange;
this.DurationRange = durationRange;
@ -71,10 +71,10 @@ public abstract class Device : IDisposable
public override bool Equals(object? obj)
{
return obj is Device d && Equals(d);
return obj is Api d && Equals(d);
}
protected bool Equals(Device other)
protected bool Equals(Api other)
{
return IntensityRange.Equals(other.IntensityRange) && DurationRange.Equals(other.DurationRange) && ApiType == other.ApiType;
}

View File

@ -7,13 +7,14 @@ using Newtonsoft.Json.Linq;
namespace CShocker.Devices.Abstract;
public abstract class OpenShockDevice : Device
public abstract class OpenShockApi : Api
{
protected readonly HttpClient HttpClient = new();
public string Endpoint { get; init; }
public string ApiKey { get; init; }
private const string DefaultEndpoint = "https://api.shocklink.net";
public OpenShockDevice(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
public OpenShockApi(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, string apiKey, string endpoint = DefaultEndpoint, ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
{
this.Endpoint = endpoint;
this.ApiKey = apiKey;
@ -21,10 +22,10 @@ public abstract class OpenShockDevice : Device
public override bool Equals(object? obj)
{
return obj is OpenShockDevice osd && Equals(osd);
return obj is OpenShockApi osd && Equals(osd);
}
private bool Equals(OpenShockDevice other)
private bool Equals(OpenShockApi other)
{
return base.Equals(other) && Endpoint == other.Endpoint && ApiKey == other.ApiKey;
}
@ -34,12 +35,18 @@ public abstract class OpenShockDevice : Device
return HashCode.Combine(Endpoint, ApiKey);
}
public List<OpenShockShocker> GetShockers()
public List<OpenShockShocker> GetShockers(string apiKey, string apiEndpoint = DefaultEndpoint,
ILogger? logger = null)
{
return GetShockers(apiKey, this, apiEndpoint, logger);
}
public static List<OpenShockShocker> GetShockers(string apiKey, OpenShockApi api, string apiEndpoint = DefaultEndpoint, ILogger? logger = null)
{
List<OpenShockShocker> shockers = new();
HttpClient httpClient = new();
HttpRequestMessage requestOwnShockers = new (HttpMethod.Get, $"{Endpoint}/1/shockers/own")
HttpRequestMessage requestOwnShockers = new (HttpMethod.Get, $"{apiEndpoint}/1/shockers/own")
{
Headers =
{
@ -47,21 +54,24 @@ public abstract class OpenShockDevice : Device
Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
}
};
requestOwnShockers.Headers.Add("OpenShockToken", ApiKey);
this.Logger?.Log(LogLevel.Debug, $"Requesting {requestOwnShockers.RequestUri}");
requestOwnShockers.Headers.Add("OpenShockToken", apiKey);
logger?.Log(LogLevel.Debug, $"Requesting {requestOwnShockers.RequestUri}");
HttpResponseMessage ownResponse = httpClient.Send(requestOwnShockers);
this.Logger?.Log(!ownResponse.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug,
logger?.Log(!ownResponse.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug,
$"{requestOwnShockers.RequestUri} response: {ownResponse.StatusCode}");
if (!ownResponse.IsSuccessStatusCode)
return shockers;
StreamReader ownShockerStreamReader = new(ownResponse.Content.ReadAsStream());
string ownShockerJson = ownShockerStreamReader.ReadToEnd();
this.Logger?.Log(LogLevel.Debug,ownShockerJson);
logger?.Log(LogLevel.Debug,ownShockerJson);
JObject ownShockerListJObj = JObject.Parse(ownShockerJson);
shockers.AddRange(ownShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t => t.ToObject<OpenShockShocker>()));
shockers.AddRange(ownShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t =>
{
return new OpenShockShocker(api, t["name"]!.Value<string>()!, t["id"]!.Value<string>()!, t["rfId"]!.Value<short>(), Enum.Parse<OpenShockShocker.OpenShockModel>(t["model"]!.Value<string>()!), t["createdOn"]!.ToObject<DateTime>(), t["isPaused"]!.Value<bool>());
}));
HttpRequestMessage requestSharedShockers = new (HttpMethod.Get, $"{Endpoint}/1/shockers/shared")
HttpRequestMessage requestSharedShockers = new (HttpMethod.Get, $"{apiEndpoint}/1/shockers/shared")
{
Headers =
{
@ -69,19 +79,22 @@ public abstract class OpenShockDevice : Device
Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
}
};
requestSharedShockers.Headers.Add("OpenShockToken", ApiKey);
this.Logger?.Log(LogLevel.Debug, $"Requesting {requestSharedShockers.RequestUri}");
requestSharedShockers.Headers.Add("OpenShockToken", apiKey);
logger?.Log(LogLevel.Debug, $"Requesting {requestSharedShockers.RequestUri}");
HttpResponseMessage sharedResponse = httpClient.Send(requestSharedShockers);
this.Logger?.Log(!sharedResponse.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug,
logger?.Log(!sharedResponse.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug,
$"{requestSharedShockers.RequestUri} response: {sharedResponse.StatusCode}");
if (!sharedResponse.IsSuccessStatusCode)
return shockers;
StreamReader sharedShockerStreamReader = new(sharedResponse.Content.ReadAsStream());
string sharedShockerJson = sharedShockerStreamReader.ReadToEnd();
this.Logger?.Log(LogLevel.Debug, sharedShockerJson);
logger?.Log(LogLevel.Debug, sharedShockerJson);
JObject sharedShockerListJObj = JObject.Parse(sharedShockerJson);
shockers.AddRange(sharedShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t => t.ToObject<OpenShockShocker>()));
shockers.AddRange(sharedShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t =>
{
return new OpenShockShocker(api, t["name"]!.Value<string>()!, t["id"]!.Value<string>()!, t["rfId"]!.Value<short>(),Enum.Parse<OpenShockShocker.OpenShockModel>(t["model"]!.Value<string>()!), t["createdOn"]!.ToObject<DateTime>(), t["isPaused"]!.Value<bool>());
}));
return shockers;
}
}

View File

@ -0,0 +1,12 @@
using CShocker.Devices.Additional;
using CShocker.Ranges;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices.Abstract;
public abstract class PiShockApi : Api
{
protected PiShockApi(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
{
}
}

View File

@ -1,12 +0,0 @@
using CShocker.Devices.Additional;
using CShocker.Ranges;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices.Abstract;
public abstract class PiShockDevice : Device
{
protected PiShockDevice(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
{
}
}

View File

@ -1,15 +1,16 @@
using CShocker.Devices.Abstract;
using CShocker.Devices.APIs;
using CShocker.Ranges;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace CShocker.Devices.Additional;
public class DeviceJsonConverter : JsonConverter
public class ApiJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(Device));
return (objectType == typeof(Api));
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)

View File

@ -1,5 +1,24 @@
namespace CShocker.Shockers.Abstract;
using CShocker.Devices.Abstract;
using CShocker.Devices.Additional;
public interface IShocker
namespace CShocker.Shockers.Abstract;
public abstract class Shocker : IDisposable
{
public Api Api { get; }
internal Shocker(Api api)
{
this.Api = api;
}
public void Control(ControlAction action, int? intensity = null, int? duration = null)
{
this.Api.Control(action, intensity, duration, this);
}
public void Dispose()
{
Api.Dispose();
}
}

View File

@ -1,4 +1,5 @@
using CShocker.Shockers.Abstract;
using CShocker.Devices.Abstract;
using CShocker.Shockers.Abstract;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -8,7 +9,7 @@ public class ShockerJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(IShocker));
return (objectType == typeof(Shocker));
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
@ -16,22 +17,22 @@ public class ShockerJsonConverter : JsonConverter
JObject jo = JObject.Load(reader);
if (jo.ContainsKey("model")) //OpenShockShocker
{
return new OpenShockShocker()
{
name = jo.SelectToken("name")!.Value<string>()!,
id = jo.SelectToken("id")!.Value<string>()!,
rfId = jo.SelectToken("rfId")!.Value<short>(),
model = (OpenShockShocker.OpenShockModel)jo.SelectToken("model")!.Value<byte>(),
createdOn = jo.SelectToken("createdOn")!.Value<DateTime>(),
isPaused = jo.SelectToken("isPaused")!.Value<bool>()
};
return new OpenShockShocker(
jo.SelectToken("api")!.ToObject<Api>()!,
jo.SelectToken("name")!.Value<string>()!,
jo.SelectToken("id")!.Value<string>()!,
jo.SelectToken("rfId")!.Value<short>(),
(OpenShockShocker.OpenShockModel)jo.SelectToken("model")!.Value<byte>(),
jo.SelectToken("createdOn")!.Value<DateTime>(),
jo.SelectToken("isPaused")!.Value<bool>()
);
}
else //PiShockShocker
{
return new PiShockShocker()
{
Code = jo.SelectToken("Code")!.Value<string>()!
};
return new PiShockShocker(
jo.SelectToken("api")!.ToObject<Api>()!,
jo.SelectToken("Code")!.Value<string>()!
);
}
throw new Exception();
}

View File

@ -1,11 +1,12 @@
using System.Diagnostics.CodeAnalysis;
using CShocker.Devices.Abstract;
using CShocker.Shockers.Abstract;
namespace CShocker.Shockers;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
[SuppressMessage("ReSharper", "InconsistentNaming")]
public struct OpenShockShocker : IShocker
public class OpenShockShocker : Shocker
{
public string name, id;
public short rfId;
@ -13,6 +14,18 @@ public struct OpenShockShocker : IShocker
public DateTime createdOn;
public bool isPaused;
public OpenShockShocker(Api api, string name, string id, short rfId, OpenShockModel model, DateTime createdOn, bool isPaused) : base (api)
{
if (api is not OpenShockApi)
throw new Exception($"API-Type {api.GetType().FullName} is not usable with Shocker {this.GetType().FullName}");
this.name = name;
this.id = id;
this.rfId = rfId;
this.model = model;
this.createdOn = createdOn;
this.isPaused = isPaused;
}
public enum OpenShockModel : byte
{
CaiXianlin = 0,

View File

@ -1,8 +1,9 @@
using CShocker.Shockers.Abstract;
using CShocker.Devices.Abstract;
using CShocker.Shockers.Abstract;
namespace CShocker.Shockers;
public struct PiShockShocker : IShocker
public class PiShockShocker : Shocker
{
public string Code;
@ -20,4 +21,11 @@ public struct PiShockShocker : IShocker
{
return Code.GetHashCode();
}
public PiShockShocker(Api api, string code) : base(api)
{
if (api is not PiShockApi)
throw new Exception($"API-Type {api.GetType().FullName} is not usable with Shocker {this.GetType().FullName}");
Code = code;
}
}