diff --git a/CShocker/CShocker.csproj b/CShocker/CShocker.csproj
index af47cc0..4f5ace4 100644
--- a/CShocker/CShocker.csproj
+++ b/CShocker/CShocker.csproj
@@ -7,7 +7,7 @@
Glax
https://github.com/C9Glax/CShocker
git
- 1.3.8
+ 2.0.0
diff --git a/CShocker/Devices/Abstract/Device.cs b/CShocker/Devices/Abstract/Device.cs
new file mode 100644
index 0000000..662661c
--- /dev/null
+++ b/CShocker/Devices/Abstract/Device.cs
@@ -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> _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;
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Devices/Abstract/OpenShockDevice.cs b/CShocker/Devices/Abstract/OpenShockDevice.cs
new file mode 100644
index 0000000..99fc951
--- /dev/null
+++ b/CShocker/Devices/Abstract/OpenShockDevice.cs
@@ -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 GetShockers()
+ {
+ List 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()));
+
+ 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()));
+ return shockers;
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Devices/Abstract/PiShockDevice.cs b/CShocker/Devices/Abstract/PiShockDevice.cs
new file mode 100644
index 0000000..79747d2
--- /dev/null
+++ b/CShocker/Devices/Abstract/PiShockDevice.cs
@@ -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)
+ {
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Devices/Abstract/SerialPortInfo.cs b/CShocker/Devices/Abstract/SerialPortInfo.cs
new file mode 100644
index 0000000..1ce7871
--- /dev/null
+++ b/CShocker/Devices/Abstract/SerialPortInfo.cs
@@ -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";
+ }
+}
diff --git a/CShocker/Shockers/ControlAction.cs b/CShocker/Devices/Additional/ControlActionEnum.cs
similarity index 64%
rename from CShocker/Shockers/ControlAction.cs
rename to CShocker/Devices/Additional/ControlActionEnum.cs
index a2c42fa..49bffb4 100644
--- a/CShocker/Shockers/ControlAction.cs
+++ b/CShocker/Devices/Additional/ControlActionEnum.cs
@@ -1,4 +1,4 @@
-namespace CShocker.Shockers;
+namespace CShocker.Devices.Additional;
public enum ControlAction
{
diff --git a/CShocker/Shockers/Abstract/ShockerApi.cs b/CShocker/Devices/Additional/DeviceApiEnum.cs
similarity index 57%
rename from CShocker/Shockers/Abstract/ShockerApi.cs
rename to CShocker/Devices/Additional/DeviceApiEnum.cs
index 89e3b65..bcd5276 100644
--- a/CShocker/Shockers/Abstract/ShockerApi.cs
+++ b/CShocker/Devices/Additional/DeviceApiEnum.cs
@@ -1,6 +1,6 @@
-namespace CShocker.Shockers.Abstract;
+namespace CShocker.Devices.Additional;
-public enum ShockerApi : byte
+public enum DeviceApi : byte
{
OpenShockHttp = 0,
OpenShockSerial = 1,
diff --git a/CShocker/Shockers/ShockerJsonConverter.cs b/CShocker/Devices/Additional/DeviceJsonConverter.cs
similarity index 68%
rename from CShocker/Shockers/ShockerJsonConverter.cs
rename to CShocker/Devices/Additional/DeviceJsonConverter.cs
index 4600880..722eb6b 100644
--- a/CShocker/Shockers/ShockerJsonConverter.cs
+++ b/CShocker/Devices/Additional/DeviceJsonConverter.cs
@@ -1,51 +1,48 @@
-using CShocker.Ranges;
-using CShocker.Shockers.Abstract;
-using CShocker.Shockers.APIS;
+using CShocker.Devices.Abstract;
+using CShocker.Ranges;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace CShocker.Shockers;
+namespace CShocker.Devices.Additional;
-public class ShockerJsonConverter : JsonConverter
+public class DeviceJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
- return (objectType == typeof(Shocker));
+ return (objectType == typeof(Device));
}
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
JObject jo = JObject.Load(reader);
- ShockerApi? apiType = (ShockerApi?)jo.SelectToken("ApiType")?.Value();
+ DeviceApi? apiType = (DeviceApi?)jo.SelectToken("ApiType")?.Value();
switch (apiType)
{
- case ShockerApi.OpenShockHttp:
+ case DeviceApi.OpenShockHttp:
return new OpenShockHttp(
- jo.SelectToken("ShockerIds")!.ToObject>()!,
jo.SelectToken("IntensityRange")!.ToObject()!,
jo.SelectToken("DurationRange")!.ToObject()!,
jo.SelectToken("ApiKey")!.Value()!,
jo.SelectToken("Endpoint")!.Value()!
);
- case ShockerApi.OpenShockSerial:
+ case DeviceApi.OpenShockSerial:
return new OpenShockSerial(
- jo.SelectToken("Model")!.ToObject>()!,
jo.SelectToken("IntensityRange")!.ToObject()!,
jo.SelectToken("DurationRange")!.ToObject()!,
- jo.SelectToken("SerialPortI")!.ToObject()!
+ jo.SelectToken("SerialPortI")!.ToObject()!,
+ jo.SelectToken("ApiKey")!.Value()!,
+ jo.SelectToken("Endpoint")!.Value()!
);
- case ShockerApi.PiShockHttp:
+ case DeviceApi.PiShockHttp:
return new PiShockHttp(
- jo.SelectToken("ShockerIds")!.ToObject>()!,
jo.SelectToken("IntensityRange")!.ToObject()!,
jo.SelectToken("DurationRange")!.ToObject()!,
jo.SelectToken("ApiKey")!.Value()!,
jo.SelectToken("Username")!.Value()!,
- jo.SelectToken("ShareCode")!.Value()!,
jo.SelectToken("Endpoint")!.Value()!
);
- case ShockerApi.PiShockSerial:
+ case DeviceApi.PiShockSerial:
throw new NotImplementedException();
default:
throw new Exception();
diff --git a/CShocker/Shockers/Abstract/SerialShocker.cs b/CShocker/Devices/Additional/SerialHelper.cs
similarity index 55%
rename from CShocker/Shockers/Abstract/SerialShocker.cs
rename to CShocker/Devices/Additional/SerialHelper.cs
index 6eb0e18..6a2c332 100644
--- a/CShocker/Shockers/Abstract/SerialShocker.cs
+++ b/CShocker/Devices/Additional/SerialHelper.cs
@@ -1,24 +1,12 @@
-using System.IO.Ports;
-using CShocker.Ranges;
-using Microsoft.Extensions.Logging;
-using System.Management;
+using System.Management;
using System.Runtime.Versioning;
+using CShocker.Devices.Abstract;
using Microsoft.Win32;
-namespace CShocker.Shockers.Abstract;
+namespace CShocker.Devices.Additional;
-public abstract class SerialShocker : Shocker
+public static class SerialHelper
{
- public SerialPortInfo SerialPortI;
- protected readonly SerialPort SerialPort;
-
- protected SerialShocker(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, SerialPortInfo serialPortI, int baudRate, ShockerApi apiType, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiType, logger)
- {
- this.SerialPortI = serialPortI;
- this.SerialPort = new SerialPort(serialPortI.PortName, baudRate);
- this.SerialPort.Open();
- }
-
[SupportedOSPlatform("windows")]
public static List GetSerialPorts()
{
@@ -56,22 +44,4 @@ public abstract class SerialShocker : Shocker
return ret;
}
- public class 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
- $"PortName: {PortName}\nDescription: {Description}\nManufacturer: {Manufacturer}\nDeviceID: {DeviceID}";
- }
- }
}
\ No newline at end of file
diff --git a/CShocker/Devices/OpenShockHttp.cs b/CShocker/Devices/OpenShockHttp.cs
new file mode 100644
index 0000000..1537be3
--- /dev/null
+++ b/CShocker/Devices/OpenShockHttp.cs
@@ -0,0 +1,64 @@
+using System.Net.Http.Headers;
+using System.Text;
+using CShocker.Devices.Abstract;
+using CShocker.Devices.Additional;
+using CShocker.Ranges;
+using CShocker.Shockers;
+using CShocker.Shockers.Abstract;
+using Microsoft.Extensions.Logging;
+
+namespace CShocker.Devices;
+
+public class OpenShockHttp : OpenShockDevice
+{
+
+
+ protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
+ {
+ if (shocker is not OpenShockShocker openShockShocker)
+ {
+ this.Logger?.Log(LogLevel.Warning, $"Shocker {shocker} is not {typeof(OpenShockShocker).FullName}");
+ return;
+ }
+
+ HttpRequestMessage request = new (HttpMethod.Post, $"{Endpoint}/2/shockers/control")
+ {
+ Headers =
+ {
+ UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
+ Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
+ },
+ Content = new StringContent("{" +
+ " \"shocks\": [" +
+ " {" +
+ $" \"id\": \"{openShockShocker.id}\"," +
+ $" \"type\": {ControlActionToByte(action)}," +
+ $" \"intensity\": {intensity}," +
+ $" \"duration\": {duration}" +
+ " }" +
+ " ]," +
+ " \"customName\": \"CShocker\"" +
+ "}", Encoding.UTF8, new MediaTypeHeaderValue("application/json"))
+ };
+ request.Headers.Add("OpenShockToken", ApiKey);
+ this.Logger?.Log(LogLevel.Debug, $"Request-Content: {request.Content.ReadAsStringAsync().Result}");
+ HttpResponseMessage response = HttpClient.Send(request);
+ this.Logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug,
+ $"{request.RequestUri} response: {response.StatusCode}");
+ }
+
+ private byte ControlActionToByte(ControlAction action)
+ {
+ return action switch
+ {
+ ControlAction.Beep => 3,
+ ControlAction.Vibrate => 2,
+ ControlAction.Shock => 1,
+ _ => 0
+ };
+ }
+
+ public OpenShockHttp(IntensityRange intensityRange, DurationRange durationRange, string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(intensityRange, durationRange, DeviceApi.OpenShockHttp, apiKey, endpoint, logger)
+ {
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Devices/OpenShockSerial.cs b/CShocker/Devices/OpenShockSerial.cs
new file mode 100644
index 0000000..8e1dad9
--- /dev/null
+++ b/CShocker/Devices/OpenShockSerial.cs
@@ -0,0 +1,51 @@
+using System.IO.Ports;
+using CShocker.Devices.Abstract;
+using CShocker.Devices.Additional;
+using CShocker.Ranges;
+using CShocker.Shockers;
+using CShocker.Shockers.Abstract;
+using Microsoft.Extensions.Logging;
+
+namespace CShocker.Devices;
+
+public class OpenShockSerial : OpenShockDevice
+{
+ private const int BaudRate = 115200;
+ public SerialPortInfo SerialPortI;
+ private readonly SerialPort _serialPort;
+
+ public OpenShockSerial(IntensityRange intensityRange, DurationRange durationRange, SerialPortInfo serialPortI, string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(intensityRange, durationRange, DeviceApi.OpenShockSerial, apiKey, endpoint, logger)
+ {
+ this.SerialPortI = serialPortI;
+ this._serialPort = new SerialPort(serialPortI.PortName, BaudRate);
+ this._serialPort.Open();
+ }
+
+ protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
+ {
+ if (shocker is not OpenShockShocker openShockShocker)
+ {
+ this.Logger?.Log(LogLevel.Warning, $"Shocker {shocker} is not {typeof(OpenShockShocker).FullName}");
+ return;
+ }
+ string json = "rftransmit {" +
+ $"\"model\":\"{Enum.GetName(openShockShocker.model)!.ToLower()}\"," +
+ $"\"id\":{openShockShocker.rfId}," +
+ $"\"type\":\"{ControlActionToString(action)}\"," +
+ $"\"intensity\":{intensity}," +
+ $"\"durationMs\":{duration}" +
+ "}";
+ _serialPort.WriteLine(json);
+ }
+
+ private static string ControlActionToString(ControlAction action)
+ {
+ return action switch
+ {
+ ControlAction.Beep => "sound",
+ ControlAction.Vibrate => "vibrate",
+ ControlAction.Shock => "shock",
+ _ => "stop"
+ };
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Devices/PiShockHttp.cs b/CShocker/Devices/PiShockHttp.cs
new file mode 100644
index 0000000..cc7da22
--- /dev/null
+++ b/CShocker/Devices/PiShockHttp.cs
@@ -0,0 +1,65 @@
+using System.Net.Http.Headers;
+using System.Text;
+using CShocker.Devices.Abstract;
+using CShocker.Devices.Additional;
+using CShocker.Ranges;
+using CShocker.Shockers;
+using CShocker.Shockers.Abstract;
+using Microsoft.Extensions.Logging;
+
+namespace CShocker.Devices;
+
+public class PiShockHttp : PiShockDevice
+{
+ // ReSharper disable twice MemberCanBePrivate.Global external usage
+ public string Username, Endpoint, ApiKey;
+ protected readonly HttpClient HttpClient = new();
+
+ public PiShockHttp(IntensityRange intensityRange, DurationRange durationRange, string apiKey, string username, string endpoint = "https://do.pishock.com/api/apioperate", ILogger? logger = null) : base(intensityRange, durationRange, DeviceApi.PiShockHttp, logger)
+ {
+ this.Username = username;
+ this.Endpoint = endpoint;
+ this.ApiKey = apiKey;
+ }
+
+ protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
+ {
+ if (shocker is not PiShockShocker piShockShocker)
+ {
+ this.Logger?.Log(LogLevel.Warning, $"Shocker {shocker} is not {typeof(OpenShockShocker).FullName}");
+ return;
+ }
+
+ HttpRequestMessage request = new (HttpMethod.Post, $"{Endpoint}")
+ {
+ Headers =
+ {
+ UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
+ Accept = { new MediaTypeWithQualityHeaderValue("text/plain") }
+ },
+ Content = new StringContent("{" +
+ $"\"Username\":\"{Username}\"," +
+ "\"Name\":\"CShocker\"," +
+ $"\"Code\":\"{piShockShocker.Code}\"," +
+ $"\"Intensity\":\"{intensity}\"," +
+ $"\"Duration\":\"{duration/1000}\"," + //duration is in seconds no ms
+ $"\"Apikey\":\"{ApiKey}\"," +
+ $"\"Op\":\"{ControlActionToByte(action)}\"" +
+ "}", Encoding.UTF8, new MediaTypeHeaderValue("application/json"))
+ };
+ HttpResponseMessage response = HttpClient.Send(request);
+ this.Logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug,
+ $"{request.RequestUri} response: {response.StatusCode}");
+ }
+
+ private byte ControlActionToByte(ControlAction action)
+ {
+ return action switch
+ {
+ ControlAction.Beep => 2,
+ ControlAction.Vibrate => 1,
+ ControlAction.Shock => 0,
+ _ => 2
+ };
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Shockers/APIS/PiShockSerial.cs b/CShocker/Devices/PiShockSerial.cs
similarity index 56%
rename from CShocker/Shockers/APIS/PiShockSerial.cs
rename to CShocker/Devices/PiShockSerial.cs
index ad95012..f32a538 100644
--- a/CShocker/Shockers/APIS/PiShockSerial.cs
+++ b/CShocker/Devices/PiShockSerial.cs
@@ -1,18 +1,26 @@
-using CShocker.Ranges;
+using System.IO.Ports;
+using CShocker.Devices.Abstract;
+using CShocker.Devices.Additional;
+using CShocker.Ranges;
using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging;
-namespace CShocker.Shockers.APIS;
+namespace CShocker.Devices;
-public class PiShockSerial : SerialShocker
+public class PiShockSerial : PiShockDevice
{
private const int BaudRate = 115200;
- public PiShockSerial(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, SerialPortInfo serialPortI, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, serialPortI, BaudRate, ShockerApi.PiShockSerial, logger)
+ public SerialPortInfo SerialPortI;
+ private readonly SerialPort _serialPort;
+
+ public PiShockSerial(IntensityRange intensityRange, DurationRange durationRange, DeviceApi apiType, SerialPortInfo serialPortI, ILogger? logger = null) : base(intensityRange, durationRange, apiType, logger)
{
+ this.SerialPortI = serialPortI;
+ this._serialPort = new SerialPort(this.SerialPortI.PortName, BaudRate);
throw new NotImplementedException();
}
-
- protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
+
+ protected override void ControlInternal(ControlAction action, IShocker shocker, int intensity, int duration)
{
string json = "{" +
"\"cmd\": \"operate\"," +
@@ -23,7 +31,7 @@ public class PiShockSerial : SerialShocker
$"\"id\": " +
"}" +
"}";
- SerialPort.WriteLine(json);
+ _serialPort.WriteLine(json);
}
private static string ControlActionToOp(ControlAction action)
diff --git a/CShocker/Shockers/APIS/OpenShockHttp.cs b/CShocker/Shockers/APIS/OpenShockHttp.cs
deleted file mode 100644
index 39fb69e..0000000
--- a/CShocker/Shockers/APIS/OpenShockHttp.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using System.Net.Http.Headers;
-using System.Text;
-using CShocker.Ranges;
-using CShocker.Shockers.Abstract;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json.Linq;
-
-namespace CShocker.Shockers.APIS;
-
-public class OpenShockHttp : HttpShocker
-{
-
- public List GetShockers()
- {
- HttpRequestMessage requestDevices = new (HttpMethod.Get, $"{Endpoint}/2/devices")
- {
- Headers =
- {
- UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
- Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
- }
- };
- requestDevices.Headers.Add("OpenShockToken", ApiKey);
- this.Logger?.Log(LogLevel.Debug, $"Requesting {requestDevices.RequestUri}");
- HttpResponseMessage responseDevices = HttpClient.Send(requestDevices);
-
- StreamReader deviceStreamReader = new(responseDevices.Content.ReadAsStream());
- string deviceJson = deviceStreamReader.ReadToEnd();
- this.Logger?.Log(!responseDevices.IsSuccessStatusCode ? LogLevel.Critical : LogLevel.Debug,
- $"{requestDevices.RequestUri} response: {responseDevices.StatusCode}\n{deviceJson}");
- JObject deviceListJObj = JObject.Parse(deviceJson);
- List deviceIds = new();
- deviceIds.AddRange(deviceListJObj["data"]!.Children()["id"].Values()!);
-
- List shockerIds = new();
- foreach (string deviceId in deviceIds)
- {
- HttpRequestMessage requestShockers = new (HttpMethod.Get, $"{Endpoint}/2/devices/{deviceId}/shockers")
- {
- Headers =
- {
- UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
- Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
- }
- };
- requestShockers.Headers.Add("OpenShockToken", ApiKey);
- this.Logger?.Log(LogLevel.Debug, $"Requesting {requestShockers.RequestUri}");
- HttpResponseMessage response = HttpClient.Send(requestShockers);
-
- StreamReader shockerStreamReader = new(response.Content.ReadAsStream());
- string shockerJson = shockerStreamReader.ReadToEnd();
- this.Logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Critical : LogLevel.Debug,
- $"{requestShockers.RequestUri} response: {response.StatusCode}\n{shockerJson}");
- JObject shockerListJObj = JObject.Parse(shockerJson);
- shockerIds.AddRange(shockerListJObj["data"]!.Children()["id"].Values()!);
-
- }
- return shockerIds;
- }
-
- protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
- {
- HttpRequestMessage request = new (HttpMethod.Post, $"{Endpoint}/2/shockers/control")
- {
- Headers =
- {
- UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
- Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
- },
- Content = new StringContent("{" +
- "\"shocks\": ["+
- "{"+
- $"\"id\": \"{shockerId}\"," +
- $"\"type\": {ControlActionToByte(action)},"+
- $"\"intensity\": {intensity},"+
- $"\"duration\": {duration}"+
- "}" +
- "]," +
- "\"customName\": CShocker" +
- "}", Encoding.UTF8, new MediaTypeHeaderValue("application/json"))
- };
- request.Headers.Add("OpenShockToken", ApiKey);
- this.Logger?.Log(LogLevel.Debug, $"Request-Content: {request.Content}");
- HttpResponseMessage response = HttpClient.Send(request);
- this.Logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Critical : LogLevel.Debug,
- $"{request.RequestUri} response: {response.StatusCode}");
- }
-
- private byte ControlActionToByte(ControlAction action)
- {
- return action switch
- {
- ControlAction.Beep => 3,
- ControlAction.Vibrate => 2,
- ControlAction.Shock => 1,
- _ => 0
- };
- }
-
- public OpenShockHttp(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiKey, endpoint, ShockerApi.OpenShockHttp, logger)
- {
- }
-}
\ No newline at end of file
diff --git a/CShocker/Shockers/APIS/OpenShockSerial.cs b/CShocker/Shockers/APIS/OpenShockSerial.cs
deleted file mode 100644
index 03b2b19..0000000
--- a/CShocker/Shockers/APIS/OpenShockSerial.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using System.Net.Http.Headers;
-using CShocker.Ranges;
-using CShocker.Shockers.Abstract;
-using Microsoft.Extensions.Logging;
-using Newtonsoft.Json.Linq;
-
-namespace CShocker.Shockers.APIS;
-
-public class OpenShockSerial : SerialShocker
-{
- // ReSharper disable once MemberCanBePrivate.Global external usage
- public readonly Dictionary Model;
- private const int BaudRate = 115200;
- public OpenShockSerial(Dictionary shockerIds, IntensityRange intensityRange, DurationRange durationRange, SerialPortInfo serialPortI, ILogger? logger = null) : base(shockerIds.Keys.ToList(), intensityRange, durationRange, serialPortI, BaudRate, ShockerApi.OpenShockSerial, logger)
- {
- this.Model = shockerIds;
- }
-
- protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
- {
- string json = "rftransmit {" +
- $"\"model\":\"{Enum.GetName(Model[shockerId])!.ToLower()}\"," +
- $"\"id\":{shockerId}," +
- $"\"type\":\"{ControlActionToString(action)}\"," +
- $"\"intensity\":{intensity}," +
- $"\"durationMs\":{duration}" +
- "}";
- SerialPort.WriteLine(json);
- }
-
- public Dictionary GetShockers(string apiEndpoint, string apiKey)
- {
- HttpClient httpClient = new();
- HttpRequestMessage requestDevices = new (HttpMethod.Get, $"{apiEndpoint}/2/devices")
- {
- Headers =
- {
- UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
- Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
- }
- };
- requestDevices.Headers.Add("OpenShockToken", apiKey);
- HttpResponseMessage responseDevices = httpClient.Send(requestDevices);
-
- StreamReader deviceStreamReader = new(responseDevices.Content.ReadAsStream());
- string deviceJson = deviceStreamReader.ReadToEnd();
- this.Logger?.Log(LogLevel.Debug, $"{requestDevices.RequestUri} response: {responseDevices.StatusCode}\n{deviceJson}");
- JObject deviceListJObj = JObject.Parse(deviceJson);
- List deviceIds = new();
- deviceIds.AddRange(deviceListJObj["data"]!.Children()["id"].Values()!);
-
- Dictionary models = new();
- foreach (string deviceId in deviceIds)
- {
- HttpRequestMessage requestShockers = new (HttpMethod.Get, $"{apiEndpoint}/2/devices/{deviceId}/shockers")
- {
- Headers =
- {
- UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
- Accept = { new MediaTypeWithQualityHeaderValue("application/json") }
- }
- };
- requestShockers.Headers.Add("OpenShockToken", apiKey);
- HttpResponseMessage response = httpClient.Send(requestShockers);
-
- StreamReader shockerStreamReader = new(response.Content.ReadAsStream());
- string shockerJson = shockerStreamReader.ReadToEnd();
- this.Logger?.Log(LogLevel.Debug, $"{requestShockers.RequestUri} response: {response.StatusCode}\n{shockerJson}");
- JObject shockerListJObj = JObject.Parse(shockerJson);
- for (int i = 0; i < shockerListJObj["data"]!.Children().Count(); i++)
- {
- models.Add(
- shockerListJObj["data"]![i]!["rfId"]!.Value().ToString(),
- Enum.Parse(shockerListJObj["data"]![i]!["model"]!.Value()!)
- );
- }
- }
-
- return models;
- }
-
- public enum ShockerModel : byte
- {
- CaiXianlin = 0,
- Petrainer = 1
- }
-
- private static string ControlActionToString(ControlAction action)
- {
- return action switch
- {
- ControlAction.Beep => "sound",
- ControlAction.Vibrate => "vibrate",
- ControlAction.Shock => "shock",
- _ => "stop"
- };
- }
-}
\ No newline at end of file
diff --git a/CShocker/Shockers/APIS/PiShockHttp.cs b/CShocker/Shockers/APIS/PiShockHttp.cs
deleted file mode 100644
index 766d09c..0000000
--- a/CShocker/Shockers/APIS/PiShockHttp.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-using System.Net.Http.Headers;
-using System.Text;
-using CShocker.Ranges;
-using CShocker.Shockers.Abstract;
-using Microsoft.Extensions.Logging;
-
-namespace CShocker.Shockers.APIS;
-
-public class PiShockHttp : HttpShocker
-{
- // ReSharper disable twice MemberCanBePrivate.Global external usage
- public readonly string Username, ShareCode;
-
- public PiShockHttp(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, string apiKey, string username, string shareCode, string endpoint = "https://do.pishock.com/api/apioperate", ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiKey, endpoint, ShockerApi.PiShockHttp, logger)
- {
- this.Username = username;
- this.ShareCode = shareCode;
- }
-
- protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
- {
- HttpRequestMessage request = new (HttpMethod.Post, $"{Endpoint}")
- {
- Headers =
- {
- UserAgent = { new ProductInfoHeaderValue("CShocker", "1") },
- Accept = { new MediaTypeWithQualityHeaderValue("text/plain") }
- },
- Content = new StringContent("{" +
- $"\"Username\":\"{Username}\"," +
- "\"Name\":\"CShocker\"," +
- $"\"Code\":\"{ShareCode}\"," +
- $"\"Intensity\":\"{intensity}\"," +
- $"\"Duration\":\"{duration/1000}\"," + //duration is in seconds no ms
- $"\"Apikey\":\"{ApiKey}\"," +
- $"\"Op\":\"{ControlActionToByte(action)}\"" +
- "}", Encoding.UTF8, new MediaTypeHeaderValue("application/json"))
- };
- HttpResponseMessage response = HttpClient.Send(request);
- this.Logger?.Log(LogLevel.Debug, $"{request.RequestUri} response: {response.StatusCode} {response.Content.ReadAsStringAsync()}");
- }
-
- private byte ControlActionToByte(ControlAction action)
- {
- return action switch
- {
- ControlAction.Beep => 2,
- ControlAction.Vibrate => 1,
- ControlAction.Shock => 0,
- _ => 2
- };
- }
-}
\ No newline at end of file
diff --git a/CShocker/Shockers/Abstract/HttpShocker.cs b/CShocker/Shockers/Abstract/HttpShocker.cs
deleted file mode 100644
index 0df4535..0000000
--- a/CShocker/Shockers/Abstract/HttpShocker.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using CShocker.Ranges;
-using Microsoft.Extensions.Logging;
-
-namespace CShocker.Shockers.Abstract;
-
-public abstract class HttpShocker : Shocker
-{
- protected readonly HttpClient HttpClient = new();
- // ReSharper disable twice MemberCanBeProtected.Global external usage
- public string Endpoint { get; init; }
- public string ApiKey { get; init; }
-
- protected HttpShocker(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, string apiKey, string endpoint, ShockerApi apiType, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiType, logger)
- {
- Endpoint = endpoint;
- ApiKey = apiKey;
- }
-
- public override string ToString()
- {
- return $"{base.ToString()}\n" +
- $"Endpoint: {Endpoint}\n" +
- $"ApiKey: {ApiKey}";
- }
-}
\ No newline at end of file
diff --git a/CShocker/Shockers/Abstract/Shocker.cs b/CShocker/Shockers/Abstract/Shocker.cs
index 7d86b00..caab141 100644
--- a/CShocker/Shockers/Abstract/Shocker.cs
+++ b/CShocker/Shockers/Abstract/Shocker.cs
@@ -1,53 +1,5 @@
-using System.Reflection.Metadata;
-using CShocker.Ranges;
-using Microsoft.Extensions.Logging;
+namespace CShocker.Shockers.Abstract;
-namespace CShocker.Shockers.Abstract;
-
-public abstract class Shocker
+public interface IShocker
{
- // ReSharper disable 4 times MemberCanBePrivate.Global external use
- public readonly List ShockerIds;
- public readonly IntensityRange IntensityRange;
- public readonly DurationRange DurationRange;
- protected ILogger? Logger;
- public readonly ShockerApi ApiType;
-
- public void Control(ControlAction action, string? shockerId = null, int? intensity = null, int? duration = null)
- {
- int i = intensity ?? IntensityRange.GetRandomRangeValue();
- int d = duration ?? DurationRange.GetRandomRangeValue();
- this.Logger?.Log(LogLevel.Information, $"{action} {(intensity is not null ? $"Overwrite {i}" : $"{i}")} {(duration is not null ? $"Overwrite {d}" : $"{d}")}");
- if (action is ControlAction.Nothing)
- return;
- if(shockerId is null)
- foreach (string shocker in ShockerIds)
- ControlInternal(action, shocker, i, d);
- else
- ControlInternal(action, shockerId, i, d);
- }
-
- protected abstract void ControlInternal(ControlAction action, string shockerId, int intensity, int duration);
-
- protected Shocker(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, ShockerApi apiType, ILogger? logger = null)
- {
- this.ShockerIds = shockerIds;
- this.IntensityRange = intensityRange;
- this.DurationRange = durationRange;
- this.ApiType = apiType;
- this.Logger = logger;
- }
-
- public void SetLogger(ILogger? logger)
- {
- this.Logger = logger;
- }
-
- public override string ToString()
- {
- return $"ShockerType: {Enum.GetName(typeof(ShockerApi), this.ApiType)}\n" +
- $"Shocker-IDs: {string.Join(", ", this.ShockerIds)}\n" +
- $"IntensityRange: {IntensityRange}\n" +
- $"DurationRange: {DurationRange}";
- }
}
\ No newline at end of file
diff --git a/CShocker/Shockers/Additional/ShockerJsonConverter.cs b/CShocker/Shockers/Additional/ShockerJsonConverter.cs
new file mode 100644
index 0000000..168279a
--- /dev/null
+++ b/CShocker/Shockers/Additional/ShockerJsonConverter.cs
@@ -0,0 +1,48 @@
+using CShocker.Shockers.Abstract;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace CShocker.Shockers.Additional;
+
+public class ShockerJsonConverter : JsonConverter
+{
+ public override bool CanConvert(Type objectType)
+ {
+ return (objectType == typeof(IShocker));
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
+ {
+ JObject jo = JObject.Load(reader);
+ if (jo.ContainsKey("model")) //OpenShockShocker
+ {
+ return new OpenShockShocker()
+ {
+ name = jo.SelectToken("name")!.Value()!,
+ id = jo.SelectToken("id")!.Value()!,
+ rfId = jo.SelectToken("rfId")!.Value(),
+ model = (OpenShockShocker.OpenShockModel)jo.SelectToken("model")!.Value(),
+ createdOn = jo.SelectToken("createdOn")!.Value(),
+ isPaused = jo.SelectToken("isPaused")!.Value()
+ };
+ }
+ else //PiShockShocker
+ {
+ return new PiShockShocker()
+ {
+ Code = jo.SelectToken("Code")!.Value()!
+ };
+ }
+ throw new Exception();
+ }
+
+ public override bool CanWrite => false;
+
+ ///
+ /// Don't call this
+ ///
+ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
+ {
+ throw new Exception("Dont call this");
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Shockers/OpenShockShocker.cs b/CShocker/Shockers/OpenShockShocker.cs
new file mode 100644
index 0000000..11590f7
--- /dev/null
+++ b/CShocker/Shockers/OpenShockShocker.cs
@@ -0,0 +1,32 @@
+using System.Diagnostics.CodeAnalysis;
+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 string name, id;
+ public short rfId;
+ public OpenShockModel model;
+ public DateTime createdOn;
+ public bool isPaused;
+
+ public enum OpenShockModel : byte
+ {
+ CaiXianlin = 0,
+ Petrainer = 1
+ }
+
+ public override string ToString()
+ {
+ return $"{GetType().Name}\n" +
+ $"Name: {name}\n" +
+ $"ID: {id}\n" +
+ $"RF-ID: {rfId}\n" +
+ $"Model: {Enum.GetName(model)}\n" +
+ $"Created On: {createdOn}\n" +
+ $"Paused: {isPaused}\n\r";
+ }
+}
\ No newline at end of file
diff --git a/CShocker/Shockers/PiShockShocker.cs b/CShocker/Shockers/PiShockShocker.cs
new file mode 100644
index 0000000..efb9e24
--- /dev/null
+++ b/CShocker/Shockers/PiShockShocker.cs
@@ -0,0 +1,8 @@
+using CShocker.Shockers.Abstract;
+
+namespace CShocker.Shockers;
+
+public struct PiShockShocker : IShocker
+{
+ public string Code;
+}
\ No newline at end of file
diff --git a/TestApp/Program.cs b/TestApp/Program.cs
index 5d8dd88..0babb27 100644
--- a/TestApp/Program.cs
+++ b/TestApp/Program.cs
@@ -1,13 +1,37 @@
-using CShocker.Ranges;
+using CShocker.Devices;
+using CShocker.Devices.Abstract;
+using CShocker.Devices.Additional;
+using CShocker.Ranges;
+using CShocker.Shockers;
using CShocker.Shockers.Abstract;
-using CShocker.Shockers.APIS;
-using Microsoft.Extensions.Logging;
+using CShocker.Shockers.Additional;
+using Newtonsoft.Json;
using TestApp;
-Logger logger = new (LogLevel.Trace);
+Logger logger = new ();
+List shockers = new();
+Console.WriteLine("OpenShock API Key:");
+string? apiKey = Console.ReadLine();
+while(apiKey is null || apiKey.Length < 1)
+ apiKey = Console.ReadLine();
+
+
+OpenShockHttp openShockHttp = new (new IntensityRange(30, 50), new DurationRange(1000, 1000), apiKey, logger: logger);
+shockers = openShockHttp.GetShockers();
+openShockHttp.Control(ControlAction.Vibrate, 20, 1000, shockers.First());
+
+File.WriteAllText("devices.json", JsonConvert.SerializeObject(openShockHttp));
+OpenShockHttp deserialized = JsonConvert.DeserializeObject(File.ReadAllText("devices.json"))!;
+Thread.Sleep(1100); //Wait for previous to end
+deserialized.Control(ControlAction.Vibrate, 20, 1000, shockers.First());
+openShockHttp.Dispose();
+deserialized.Dispose();
+
+
+/*
#pragma warning disable CA1416
-List serialPorts = SerialDevice.GetSerialPorts();
+List serialPorts = SerialHelper.GetSerialPorts();
if (serialPorts.Count < 1)
return;
@@ -17,13 +41,23 @@ for(int i = 0; i < serialPorts.Count; i++)
Console.WriteLine($"Select Serial Port [0-{serialPorts.Count-1}]:");
string? selectedPortStr = Console.ReadLine();
-int selectedPort = -1;
+int selectedPort;
while (!int.TryParse(selectedPortStr, out selectedPort) || selectedPort < 0 || selectedPort > serialPorts.Count - 1)
{
Console.WriteLine($"Select Serial Port [0-{serialPorts.Count-1}]:");
selectedPortStr = Console.ReadLine();
}
-OpenShockSerial shockSerial = new (new Dictionary(), new IntensityRange(30,50), new DurationRange(1000,1000), serialPorts[selectedPort], logger);
-Dictionary shockers = shockSerial.GetShockers("https://api.shocklink.net", "LOLAPIKEY");
-Console.ReadKey();
\ No newline at end of file
+OpenShockSerial openShockSerial = new(new IntensityRange(30, 50), new DurationRange(1000, 1000),serialPorts[selectedPort], apiKey, logger: logger);
+shockers = openShockSerial.GetShockers();
+openShockSerial.Control(ControlAction.Vibrate, 20, 1000, shockers.First());
+File.WriteAllText("devices.json", JsonConvert.SerializeObject(openShockSerial));
+OpenShockHttp deserialized = JsonConvert.DeserializeObject(File.ReadAllText("devices.json"))!;
+openShockSerial.Dispose();
+deserialized.Dispose();
+*/
+
+foreach(OpenShockShocker s in shockers)
+ Console.Write(s);
+File.WriteAllText("shockers.json", JsonConvert.SerializeObject(shockers));
+List deserializedShockers = JsonConvert.DeserializeObject>(File.ReadAllText("shockers.json"), new ShockerJsonConverter())!;
\ No newline at end of file