Fix OpenShockHttp: Wrong json caused Bad Request

Get OpenShock Shockers from API.
Save Shockers for PiShock and OpenShock in different structs
Implement Action Queue, to avoid synchronous actions getting lost.

Moved SerialPortInfo to own file
Created ShockerJsonConverter
Better separation of Devices/APIs and Shockers
This commit is contained in:
2024-01-29 15:37:19 +01:00
parent e255caeb64
commit ac19e20fb7
22 changed files with 530 additions and 399 deletions

View File

@ -0,0 +1,77 @@
using CShocker.Devices.Additional;
using CShocker.Ranges;
using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging;
namespace CShocker.Devices.Abstract;
public abstract class Device : 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 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)
{
int i = intensity ?? IntensityRange.GetRandomRangeValue();
int d = duration ?? DurationRange.GetRandomRangeValue();
if (action is ControlAction.Nothing)
{
this.Logger?.Log(LogLevel.Information, "Doing nothing");
return;
}
foreach (IShocker 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 Device(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, ILogger? logger = null)
{
Thread workQueueThread;
this.IntensityRange = intensityRange;
this.DurationRange = durationRange;
this.ApiType = apiType;
this.Logger = logger;
this._workQueueThread = new Thread(QueueThread);
this._workQueueThread.Start();
}
private void QueueThread()
{
while (_workQueue)
if (_queue.Count > 0 && _queue.Dequeue() is { } action)
{
this.Logger?.Log(LogLevel.Information, $"{action.Item1} {action.Item2} {action.Item3} {action.Item4}");
ControlInternal(action.Item1, action.Item2, action.Item3, action.Item4);
Thread.Sleep(action.Item4 + CommandDelay);
}
}
public void SetLogger(ILogger? logger)
{
this.Logger = logger;
}
public override string ToString()
{
return $"ShockerType: {Enum.GetName(typeof(DeviceApi), this.ApiType)}\n" +
$"IntensityRange: {IntensityRange}\n" +
$"DurationRange: {DurationRange}\n\r";
}
public void Dispose()
{
_workQueue = false;
}
}

View File

@ -0,0 +1,72 @@
using System.Net.Http.Headers;
using CShocker.Devices.Additional;
using CShocker.Ranges;
using CShocker.Shockers;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace CShocker.Devices.Abstract;
public abstract class OpenShockDevice : Device
{
protected readonly HttpClient HttpClient = new();
public string Endpoint { get; init; }
public string ApiKey { get; init; }
public OpenShockDevice(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
{
this.Endpoint = endpoint;
this.ApiKey = apiKey;
}
public List<OpenShockShocker> GetShockers()
{
List<OpenShockShocker> shockers = new();
HttpClient httpClient = new();
HttpRequestMessage requestOwnShockers = new (HttpMethod.Get, $"{Endpoint}/1/shockers/own")
{
Headers =
{
UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
}
};
requestOwnShockers.Headers.Add("OpenShockToken", ApiKey);
this.Logger?.Log(LogLevel.Debug, $"Requesting {requestOwnShockers.RequestUri}");
HttpResponseMessage ownResponse = httpClient.Send(requestOwnShockers);
this.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);
JObject ownShockerListJObj = JObject.Parse(ownShockerJson);
shockers.AddRange(ownShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t => t.ToObject<OpenShockShocker>()));
HttpRequestMessage requestSharedShockers = new (HttpMethod.Get, $"{Endpoint}/1/shockers/shared")
{
Headers =
{
UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
}
};
requestSharedShockers.Headers.Add("OpenShockToken", ApiKey);
this.Logger?.Log(LogLevel.Debug, $"Requesting {requestSharedShockers.RequestUri}");
HttpResponseMessage sharedResponse = httpClient.Send(requestSharedShockers);
this.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);
JObject sharedShockerListJObj = JObject.Parse(sharedShockerJson);
shockers.AddRange(sharedShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t => t.ToObject<OpenShockShocker>()));
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 PiShockDevice : Device
{
protected PiShockDevice(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
{
}
}

View File

@ -0,0 +1,20 @@
namespace CShocker.Devices.Abstract;
public struct SerialPortInfo
{
public readonly string? PortName, Description, Manufacturer, DeviceID;
public SerialPortInfo(string? portName, string? description, string? manufacturer, string? deviceID)
{
this.PortName = portName;
this.Description = description;
this.Manufacturer = manufacturer;
this.DeviceID = deviceID;
}
public override string ToString()
{
return
$"{GetType().Name}\nPortName: {PortName}\nDescription: {Description}\nManufacturer: {Manufacturer}\nDeviceID: {DeviceID}\n\r";
}
}