Compare commits

...

27 Commits

Author SHA1 Message Date
34fb4d89bd Fix Default endpoint for openshock 2025-01-16 22:40:49 +01:00
828a1a2bfa Test program 2025-01-16 22:36:57 +01:00
0b98638cd5 Less CPU usage by creating tasks per action, instead of thread 2025-01-16 22:36:52 +01:00
74d30ba1b9 Fix Openshock default endpoint 2025-01-16 22:36:24 +01:00
385dba1cf3 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	CShocker/Devices/Additional/ApiHttpClient.cs
2025-01-16 21:47:48 +01:00
1ff67a5b8b Fix Assembly string empty on Publish 2025-01-16 21:46:13 +01:00
c55592cd90 Fix Assembly string empty on Publish 2025-01-16 21:38:30 +01:00
b497117b62 Change OpenShock default Endpoint 2025-01-16 20:56:34 +01:00
73c19a3f8f v3.0 Upgrade to .net9.0 2025-01-16 20:55:24 +01:00
318598f62a 2.5.1 Make HTTP Default Endpoint public 2024-11-03 01:15:26 +01:00
cf0a7c630d Change Program.cs in testapp 2024-11-03 01:11:40 +01:00
ce0a287e4e 2.5.0
Update DefaultEndpoint
Update ControlActionEnum.cs
2024-11-03 01:11:33 +01:00
7693aa8b09 2.4.2 2024-11-03 00:35:59 +01:00
746e153cb5 Fix ShockerJsonConverter 2024-02-15 23:11:56 +01:00
60746d66df Version 2024-02-12 03:07:18 +01:00
eebf15804a ToString fancy 2024-02-12 03:06:28 +01:00
2c7da0352b Fancier output 2024-02-12 02:05:22 +01:00
17206bfb8c Let Constructor throw Exception instead of method 2024-02-12 02:05:13 +01:00
4839c12340 Add RandomValueWithinLimits to IntegerRange 2024-02-12 02:04:58 +01:00
1eed03ac14 GetHashcode and Equals usage 2024-02-12 02:04:45 +01:00
f6e8ddb91d ToString more conventional 2024-02-12 02:04:09 +01:00
8d70879c68 Capitalized FieldNames onn public fields 2024-02-12 02:03:35 +01:00
639b813fb6 Dependency from Nuget 2024-02-12 02:02:45 +01:00
171b057d58 Renamed to IsValueWithinLimits 2024-02-12 02:02:30 +01:00
bb8959160a Mark exposed fields and methods 2024-02-12 02:02:10 +01:00
cbca4fd7de ValidIntensityRange and ValidDurationRange internal readonly 2024-02-11 23:24:25 +01:00
4e79f40e80 Update Readme 2024-02-11 22:58:53 +01:00
21 changed files with 178 additions and 123 deletions

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="LocationsWithSilentDeleteHolder">
<option name="locations">
<list>
<option value="$PROJECT_DIR$/CShocker/bin/Release/net9.0/win-x64" />
<option value="$PROJECT_DIR$/CShocker/bin/Release/net9.0/linux-x64/publish" />
<option value="$PROJECT_DIR$/CShocker/bin/Release/net9.0/win-x64/publish" />
</list>
</option>
</component>
</project>

View File

@ -1,5 +1,7 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CUsers_005CGlax_005CRiderProjects_005CGlaxLogger_005CGlaxLogger_005Cbin_005CDebug_005Cnet7_002E0_005CGlaxLogger_002Edll/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/AddReferences/RecentPaths/=C_003A_005CUsers_005CGlax_005CRiderProjects_005CGlaxLogger_005CGlaxLogger_005Cbin_005CDebug_005Cnet7_002E0_005CGlaxLogger_002Edll/@EntryIndexedValue">True</s:Boolean>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AHttpClient_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Fc439425da351c75ac7d966a1cc8324b51a9c471865af79d2f2f3fcb65e392_003FHttpClient_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AThrowHelper_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2c8e7ca976f350cba9836d5565dac56b11e0b56656fa786460eb1395857a6fa_003FThrowHelper_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;&#xD; <s:String x:Key="/Default/Environment/AssemblyExplorer/XmlDocument/@EntryValue">&lt;AssemblyExplorer&gt;&#xD;
&lt;Assembly Path="C:\Users\Glax\RiderProjects\GlaxLogger\GlaxLogger\bin\Debug\net7.0\GlaxLogger.dll" /&gt;&#xD; &lt;Assembly Path="C:\Users\Glax\RiderProjects\GlaxLogger\GlaxLogger\bin\Debug\net7.0\GlaxLogger.dll" /&gt;&#xD;
&lt;/AssemblyExplorer&gt;</s:String></wpf:ResourceDictionary> &lt;/AssemblyExplorer&gt;</s:String></wpf:ResourceDictionary>

View File

@ -1,20 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Authors>Glax</Authors> <Authors>Glax</Authors>
<RepositoryUrl>https://github.com/C9Glax/CShocker</RepositoryUrl> <RepositoryUrl>https://github.com/C9Glax/CShocker</RepositoryUrl>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<Version>2.3.0</Version> <Version>3.1.0</Version>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<LangVersion>latestmajor</LangVersion>
<PackageProjectUrl>https://github.com/C9Glax/CShocker</PackageProjectUrl>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" /> <PackageReference Include="System.IO.Ports" Version="9.0.1" />
<PackageReference Include="System.Management" Version="8.0.0" /> <PackageReference Include="System.Management" Version="9.0.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -19,8 +19,8 @@ public class OpenShockHttp : OpenShockApi
string json = "{" + string json = "{" +
" \"shocks\": [" + " \"shocks\": [" +
" {" + " {" +
$" \"id\": \"{openShockShocker.id}\"," + $" \"id\": \"{openShockShocker.ID}\"," +
$" \"type\": {ControlActionToByte(action)}," + $" \"type\": \"{Enum.GetName(action)}\"," +
$" \"intensity\": {intensity}," + $" \"intensity\": {intensity}," +
$" \"duration\": {duration}" + $" \"duration\": {duration}" +
" }" + " }" +
@ -31,18 +31,7 @@ public class OpenShockHttp : OpenShockApi
ApiHttpClient.MakeAPICall(HttpMethod.Post, $"{Endpoint}/2/shockers/control", json, this.Logger, new ValueTuple<string, string>("OpenShockToken", ApiKey)); ApiHttpClient.MakeAPICall(HttpMethod.Post, $"{Endpoint}/2/shockers/control", json, this.Logger, new ValueTuple<string, string>("OpenShockToken", ApiKey));
} }
private byte ControlActionToByte(ControlAction action) public OpenShockHttp(string apiKey, string? endpoint = null, ILogger? logger = null) : base(DeviceApi.OpenShockHttp, apiKey, endpoint??DefaultEndpoint, logger)
{
return action switch
{
ControlAction.Beep => 3,
ControlAction.Vibrate => 2,
ControlAction.Shock => 1,
_ => 0
};
}
public OpenShockHttp(string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(DeviceApi.OpenShockHttp, apiKey, endpoint, logger)
{ {
} }
} }

View File

@ -13,7 +13,7 @@ public class OpenShockSerial : OpenShockApi
public SerialPortInfo SerialPortI; public SerialPortInfo SerialPortI;
private readonly SerialPort _serialPort; private readonly SerialPort _serialPort;
public OpenShockSerial(SerialPortInfo serialPortI, string apiKey, string endpoint = "https://api.shocklink.net", ILogger? logger = null) : base(DeviceApi.OpenShockSerial, apiKey, endpoint, logger) public OpenShockSerial(SerialPortInfo serialPortI, string apiKey, string? endpoint = null, ILogger? logger = null) : base(DeviceApi.OpenShockSerial, apiKey, endpoint??DefaultEndpoint, logger)
{ {
this.SerialPortI = serialPortI; this.SerialPortI = serialPortI;
this._serialPort = new SerialPort(serialPortI.PortName, BaudRate); this._serialPort = new SerialPort(serialPortI.PortName, BaudRate);
@ -36,8 +36,8 @@ public class OpenShockSerial : OpenShockApi
return; return;
} }
string json = "rftransmit {" + string json = "rftransmit {" +
$"\"model\":\"{Enum.GetName(openShockShocker.model)!.ToLower()}\"," + $"\"model\":\"{Enum.GetName(openShockShocker.Model)!.ToLower()}\"," +
$"\"id\":{openShockShocker.rfId}," + $"\"id\":{openShockShocker.RfId}," +
$"\"type\":\"{ControlActionToString(action)}\"," + $"\"type\":\"{ControlActionToString(action)}\"," +
$"\"intensity\":{intensity}," + $"\"intensity\":{intensity}," +
$"\"durationMs\":{duration}" + $"\"durationMs\":{duration}" +

View File

@ -8,8 +8,8 @@ namespace CShocker.Devices.APIs;
public class PiShockHttp : PiShockApi public class PiShockHttp : PiShockApi
{ {
// ReSharper disable twice MemberCanBePrivate.Global external usage // ReSharper disable thrice MemberCanBePrivate.Global -> Exposed
public string Username, Endpoint, ApiKey; public readonly string Username, Endpoint, ApiKey;
public PiShockHttp(string apiKey, string username, string endpoint = "https://do.pishock.com/api/apioperate", ILogger? logger = null) : base(DeviceApi.PiShockHttp, logger) public PiShockHttp(string apiKey, string username, string endpoint = "https://do.pishock.com/api/apioperate", ILogger? logger = null) : base(DeviceApi.PiShockHttp, logger)
{ {

View File

@ -1,7 +1,6 @@
using System.IO.Ports; using System.IO.Ports;
using CShocker.Devices.Abstract; using CShocker.Devices.Abstract;
using CShocker.Devices.Additional; using CShocker.Devices.Additional;
using CShocker.Ranges;
using CShocker.Shockers.Abstract; using CShocker.Shockers.Abstract;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -10,7 +9,8 @@ namespace CShocker.Devices.APIs;
public class PiShockSerial : PiShockApi public class PiShockSerial : PiShockApi
{ {
private const int BaudRate = 115200; private const int BaudRate = 115200;
public SerialPortInfo SerialPortI; // ReSharper disable once MemberCanBePrivate.Global -> Exposed
public readonly SerialPortInfo SerialPortI;
private readonly SerialPort _serialPort; private readonly SerialPort _serialPort;
public PiShockSerial(DeviceApi apiType, SerialPortInfo serialPortI, ILogger? logger = null) : base(apiType, logger) public PiShockSerial(DeviceApi apiType, SerialPortInfo serialPortI, ILogger? logger = null) : base(apiType, logger)

View File

@ -7,30 +7,24 @@ namespace CShocker.Devices.Abstract;
public abstract class Api : IDisposable public abstract class Api : IDisposable
{ {
// ReSharper disable 4 times MemberCanBePrivate.Global external use // ReSharper disable 4 times MemberCanBePrivate.Global -> Exposed
protected ILogger? Logger; protected ILogger? Logger;
public readonly DeviceApi ApiType; public readonly DeviceApi ApiType;
private readonly Queue<ValueTuple<ControlAction, Shocker, int, int>> _queue = new(); private Queue<DateTime> order = new();
private bool _workOnQueue = true; private Dictionary<DateTime, Task> tasks = new();
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly Thread _workQueueThread;
private const short CommandDelay = 50; private const short CommandDelay = 50;
public IntegerRange ValidIntensityRange, ValidDurationRange; internal readonly IntegerRange ValidIntensityRange, ValidDurationRange;
internal void Control(ControlAction action, int intensity, int duration, params Shocker[] shockers) internal void Control(ControlAction action, int intensity, int duration, params Shocker[] shockers)
{ {
bool enqueueItem = true; bool enqueueItem = true;
if (action is ControlAction.Nothing) if (!ValidIntensityRange.IsValueWithinLimits(intensity))
{
this.Logger?.Log(LogLevel.Information, "No action defined.");
enqueueItem = false;
}
if (!ValidIntensityRange.ValueWithinLimits(intensity))
{ {
this.Logger?.Log(LogLevel.Information, $"Value not within allowed {nameof(intensity)}-Range ({ValidIntensityRange.RangeString()}): {intensity}"); this.Logger?.Log(LogLevel.Information, $"Value not within allowed {nameof(intensity)}-Range ({ValidIntensityRange.RangeString()}): {intensity}");
enqueueItem = false; enqueueItem = false;
} }
if (!ValidDurationRange.ValueWithinLimits(duration)) if (!ValidDurationRange.IsValueWithinLimits(duration))
{ {
this.Logger?.Log(LogLevel.Information, $"Value not within allowed {nameof(duration)}-Range ({ValidIntensityRange.RangeString()}): {duration}"); this.Logger?.Log(LogLevel.Information, $"Value not within allowed {nameof(duration)}-Range ({ValidIntensityRange.RangeString()}): {duration}");
enqueueItem = false; enqueueItem = false;
@ -42,8 +36,13 @@ public abstract class Api : IDisposable
} }
foreach (Shocker shocker in shockers) foreach (Shocker shocker in shockers)
{ {
this.Logger?.Log(LogLevel.Debug, $"Enqueueing {action} {intensity} {duration}"); this.Logger?.Log(LogLevel.Debug, $"Enqueueing {action} Intensity: {intensity} Duration: {duration}\nShocker:\n{shocker}");
_queue.Enqueue(new(action, shocker, intensity, duration)); ValueTuple<ControlAction, Shocker, int, int> tuple = new(action, shocker, intensity, duration);
DateTime now = DateTime.Now;
Task t = new (() => ExecuteTask(now, tuple));
order.Enqueue(now);
tasks.Add(now, t);
t.Start();
} }
} }
@ -55,19 +54,17 @@ public abstract class Api : IDisposable
this.Logger = logger; this.Logger = logger;
this.ValidIntensityRange = validIntensityRange; this.ValidIntensityRange = validIntensityRange;
this.ValidDurationRange = validDurationRange; this.ValidDurationRange = validDurationRange;
this._workQueueThread = new Thread(QueueThread);
this._workQueueThread.Start();
} }
private void QueueThread() private void ExecuteTask(DateTime when, ValueTuple<ControlAction, Shocker, int, int> tuple)
{ {
while (_workOnQueue) while (order.First() != when)
if (_queue.Count > 0 && _queue.Dequeue() is { } action) Thread.Sleep(CommandDelay);
{ this.Logger?.Log(LogLevel.Information, $"Executing: {Enum.GetName(tuple.Item1)} Intensity: {tuple.Item3} Duration: {tuple.Item4}\nShocker:\n{tuple.Item2}");
this.Logger?.Log(LogLevel.Information, $"{action.Item1} {action.Item2} {action.Item3} {action.Item4}"); ControlInternal(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
ControlInternal(action.Item1, action.Item2, action.Item3, action.Item4); Thread.Sleep(tuple.Item4);
Thread.Sleep(action.Item4 + CommandDelay); tasks.Remove(when);
} order.Dequeue();
} }
public void SetLogger(ILogger? logger) public void SetLogger(ILogger? logger)
@ -92,11 +89,12 @@ public abstract class Api : IDisposable
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(ApiType); return ApiType.GetHashCode();
} }
public void Dispose() public void Dispose()
{ {
_workOnQueue = false; foreach ((DateTime when, Task? task) in tasks)
task?.Dispose();
} }
} }

View File

@ -8,10 +8,12 @@ namespace CShocker.Devices.Abstract;
public abstract class OpenShockApi : Api public abstract class OpenShockApi : Api
{ {
// ReSharper disable twice MemberCanBeProtected.Global -> Exposed
public string Endpoint { get; init; } public string Endpoint { get; init; }
public string ApiKey { get; init; } public string ApiKey { get; init; }
private const string DefaultEndpoint = "https://api.shocklink.net"; public const string DefaultEndpoint = "https://api.openshock.app";
// ReSharper disable once PublicConstructorInAbstractClass -> Exposed
public OpenShockApi(DeviceApi apiType, string apiKey, string endpoint = DefaultEndpoint, ILogger? logger = null) : base(apiType, new IntegerRange(0, 100), new IntegerRange(300, 30000), logger) public OpenShockApi(DeviceApi apiType, string apiKey, string endpoint = DefaultEndpoint, ILogger? logger = null) : base(apiType, new IntegerRange(0, 100), new IntegerRange(300, 30000), logger)
{ {
this.Endpoint = endpoint; this.Endpoint = endpoint;
@ -25,7 +27,7 @@ public abstract class OpenShockApi : Api
private bool Equals(OpenShockApi other) private bool Equals(OpenShockApi other)
{ {
return base.Equals(other) && Endpoint == other.Endpoint && ApiKey == other.ApiKey; return base.Equals(other) && Endpoint.Equals(other.Endpoint) && ApiKey.Equals(other.ApiKey);
} }
public override int GetHashCode() public override int GetHashCode()
@ -33,12 +35,13 @@ public abstract class OpenShockApi : Api
return HashCode.Combine(Endpoint, ApiKey); return HashCode.Combine(Endpoint, ApiKey);
} }
public List<OpenShockShocker> GetShockers() public IEnumerable<OpenShockShocker> GetShockers()
{ {
return GetShockers(this.ApiKey, this, this.Endpoint, this.Logger); return GetShockers(this.ApiKey, this, this.Endpoint, this.Logger);
} }
public static List<OpenShockShocker> GetShockers(string apiKey, OpenShockApi api, string apiEndpoint = DefaultEndpoint, ILogger? logger = null) // ReSharper disable once MemberCanBePrivate.Global
public static IEnumerable<OpenShockShocker> GetShockers(string apiKey, OpenShockApi api, string apiEndpoint = DefaultEndpoint, ILogger? logger = null)
{ {
List<OpenShockShocker> shockers = new(); List<OpenShockShocker> shockers = new();

View File

@ -1,7 +1,8 @@
namespace CShocker.Devices.Abstract; namespace CShocker.Devices.Abstract;
public struct SerialPortInfo public readonly struct SerialPortInfo
{ {
// ReSharper disable thrice MemberCanBePrivate.Global -> Exposed
public readonly string? PortName, Description, Manufacturer, DeviceID; public readonly string? PortName, Description, Manufacturer, DeviceID;
public SerialPortInfo(string? portName, string? description, string? manufacturer, string? deviceID) public SerialPortInfo(string? portName, string? description, string? manufacturer, string? deviceID)
@ -14,7 +15,12 @@ public struct SerialPortInfo
public override string ToString() public override string ToString()
{ {
return return $"{string.Join("\n\t",
$"{GetType().Name}\nPortName: {PortName}\nDescription: {Description}\nManufacturer: {Manufacturer}\nDeviceID: {DeviceID}\n\r"; $"{GetType().Name}",
$"PortName: {PortName}",
$"Description: {Description}",
$"Manufacturer: {Manufacturer}",
$"DeviceID: {DeviceID}")}" +
$"\n\r";
} }
} }

View File

@ -8,32 +8,58 @@ namespace CShocker.Devices.Additional;
public static class ApiHttpClient public static class ApiHttpClient
{ {
internal static HttpResponseMessage MakeAPICall(HttpMethod method, string uri, string? jsonContent, ILogger? logger = null, params ValueTuple<string, string>[] customHeaders) private static readonly ProductInfoHeaderValue UserAgent = GetUserAgent();
private static ProductInfoHeaderValue GetUserAgent()
{ {
Assembly assembly = Assembly.GetExecutingAssembly(); Assembly assembly = Assembly.GetExecutingAssembly();
FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location); FileVersionInfo fvi;
ProductInfoHeaderValue userAgent = new (fvi.ProductName ?? fvi.FileName, fvi.ProductVersion); if (assembly.Location == String.Empty)
{
DirectoryInfo dir = new (AppContext.BaseDirectory);
FileInfo? f = dir.GetFiles("*.exe").FirstOrDefault();
if (f is null)
return new("CShocker", "Release");
fvi = FileVersionInfo.GetVersionInfo(f.FullName);
}
else
{
fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
}
return new (fvi.ProductName ?? fvi.FileName, fvi.ProductVersion);
}
internal static HttpResponseMessage MakeAPICall(HttpMethod method, string uri, string? jsonContent, ILogger? logger = null, params ValueTuple<string, string>[] customHeaders)
{
HttpRequestMessage request = new (method, uri) HttpRequestMessage request = new (method, uri)
{ {
Headers = Headers =
{ {
UserAgent = { userAgent }, UserAgent = { UserAgent },
Accept = { new MediaTypeWithQualityHeaderValue("application/json") } Accept = { new MediaTypeWithQualityHeaderValue("application/json") },
} }
}; };
if (jsonContent is not null && jsonContent.Length > 0) if (jsonContent is not null && jsonContent.Length > 0)
request.Content = request.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(jsonContent))
new StringContent(jsonContent, Encoding.UTF8, new MediaTypeHeaderValue("application/json")); {
Headers = { ContentType = MediaTypeHeaderValue.Parse("application/json") }
};
foreach ((string, string) customHeader in customHeaders) foreach ((string, string) customHeader in customHeaders)
request.Headers.Add(customHeader.Item1, customHeader.Item2); request.Headers.Add(customHeader.Item1, customHeader.Item2);
logger?.Log(LogLevel.Debug, $"Request-URI: {request.RequestUri}\n\r" + logger?.Log(LogLevel.Debug, string.Join("\n\t",
$"Request-Headers: \n\t{string.Join("\n\t", request.Headers.Select(h => $"{h.Key} {string.Join(", ", h.Value)}"))}\n\r" + "Request:",
$"Request-Content: {request.Content?.ReadAsStringAsync().Result}"); $"\u251c\u2500\u2500 URI: {request.RequestUri}",
$"\u251c\u2500\u2510 Headers: {string.Concat(request.Headers.Select(h => $"\n\t\u2502 {(request.Headers.Last().Key.Equals(h.Key) ? "\u2514" : "\u251c")} {h.Key}: {string.Join(", ", h.Value)}"))}",
$"\u2514\u2500\u2500 Content: {request.Content?.ReadAsStringAsync().Result}"));
HttpClient httpClient = new(); HttpClient httpClient = new();
HttpResponseMessage response = httpClient.Send(request); HttpResponseMessage response = httpClient.Send(request);
logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug, logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug, string.Join("\n\t",
$"{request.RequestUri} response: {response.StatusCode}"); "Response:",
$"\u251c\u2500\u2500 URI: {request.RequestUri}",
$"\u251c\u2500\u2510 Headers: {string.Concat(response.Headers.Select(h => $"\n\t\u2502 {(response.Headers.Last().Key.Equals(h.Key) ? "\u2514" : "\u251c")} {h.Key}: {string.Join(", ", h.Value)}"))}",
$"\u2514\u2500\u2500 Content: {response.Content.ReadAsStringAsync().Result}"));
httpClient.Dispose(); httpClient.Dispose();
return response; return response;
} }

View File

@ -26,7 +26,7 @@ public class ApiJsonConverter : JsonConverter
case DeviceApi.PiShockHttp: case DeviceApi.PiShockHttp:
return jo.ToObject<PiShockHttp>()!; return jo.ToObject<PiShockHttp>()!;
case DeviceApi.PiShockSerial: case DeviceApi.PiShockSerial:
throw new NotImplementedException(); return jo.ToObject<PiShockSerial>()!;
default: default:
throw new Exception(); throw new Exception();
} }

View File

@ -5,5 +5,5 @@ public enum ControlAction
Beep, Beep,
Vibrate, Vibrate,
Shock, Shock,
Nothing Stop
} }

View File

@ -2,6 +2,7 @@
public readonly struct IntegerRange public readonly struct IntegerRange
{ {
// ReSharper disable twice MemberCanBePrivate.Global -> Exposed
public readonly int Min, Max; public readonly int Min, Max;
public IntegerRange(int min, int max) public IntegerRange(int min, int max)
@ -10,11 +11,16 @@ public readonly struct IntegerRange
this.Max = max; this.Max = max;
} }
public bool ValueWithinLimits(int value) public bool IsValueWithinLimits(int value)
{ {
return value >= this.Min && value <= this.Max; return value >= this.Min && value <= this.Max;
} }
public int RandomValueWithinLimits()
{
return Random.Shared.Next(this.Min, this.Max);
}
internal string RangeString() internal string RangeString()
{ {
return $"{this.Min}-{this.Max}"; return $"{this.Min}-{this.Max}";

View File

@ -5,6 +5,7 @@ namespace CShocker.Shockers.Abstract;
public abstract class Shocker : IDisposable public abstract class Shocker : IDisposable
{ {
// ReSharper disable once MemberCanBePrivate.Global -> Exposed
public Api Api { get; } public Api Api { get; }
internal Shocker(Api api) internal Shocker(Api api)

View File

@ -14,7 +14,7 @@ public class ShockerJsonConverter : JsonConverter
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{ {
JObject jo = JObject.Load(reader); JObject jo = JObject.Load(reader);
if (jo.ContainsKey("model")) //OpenShockShocker if (jo.ContainsKey("Model")) //OpenShockShocker
{ {
return jo.ToObject<OpenShockShocker>(serializer)!; return jo.ToObject<OpenShockShocker>(serializer)!;
} }
@ -22,7 +22,6 @@ public class ShockerJsonConverter : JsonConverter
{ {
return jo.ToObject<PiShockShocker>(serializer)!; return jo.ToObject<PiShockShocker>(serializer)!;
} }
throw new Exception();
} }
public override bool CanWrite => false; public override bool CanWrite => false;
@ -32,6 +31,6 @@ public class ShockerJsonConverter : JsonConverter
/// </summary> /// </summary>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{ {
throw new Exception("Dont call this"); throw new NotImplementedException("Dont call this");
} }
} }

View File

@ -1,29 +1,27 @@
using System.Diagnostics.CodeAnalysis; using CShocker.Devices.Abstract;
using CShocker.Devices.Abstract;
using CShocker.Shockers.Abstract; using CShocker.Shockers.Abstract;
namespace CShocker.Shockers; 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 class OpenShockShocker : Shocker public class OpenShockShocker : Shocker
{ {
public string name, id; // ReSharper disable thrice MemberCanBePrivate.Global -> Exposed
public short rfId; public readonly string Name, ID;
public OpenShockModel model; public readonly short RfId;
public DateTime createdOn; public readonly OpenShockModel Model;
public bool isPaused; public readonly DateTime CreatedOn;
public readonly bool IsPaused;
public OpenShockShocker(Api api, string name, string id, short rfId, OpenShockModel model, DateTime createdOn, bool isPaused) : base (api) public OpenShockShocker(Api api, string name, string id, short rfId, OpenShockModel model, DateTime createdOn, bool isPaused) : base (api)
{ {
if (api is not OpenShockApi) if (api is not OpenShockApi)
throw new Exception($"API-Type {api.GetType().FullName} is not usable with Shocker {this.GetType().FullName}"); throw new Exception($"API-Type {api.GetType().FullName} is not usable with Shocker {this.GetType().FullName}");
this.name = name; this.Name = name;
this.id = id; this.ID = id;
this.rfId = rfId; this.RfId = rfId;
this.model = model; this.Model = model;
this.createdOn = createdOn; this.CreatedOn = createdOn;
this.isPaused = isPaused; this.IsPaused = isPaused;
} }
public enum OpenShockModel : byte public enum OpenShockModel : byte
@ -34,13 +32,16 @@ public class OpenShockShocker : Shocker
public override string ToString() public override string ToString()
{ {
return $"{GetType().Name}\n" + const int tabWidth = -12;
$"Name: {name}\n" + return $"\t{string.Join("\n\t",
$"ID: {id}\n" + $"\u251c {"Type",tabWidth}: {GetType().Name}",
$"RF-ID: {rfId}\n" + $"\u251c {"Name",tabWidth}: {Name}",
$"Model: {Enum.GetName(model)}\n" + $"\u251c {"ID",tabWidth}: {ID}",
$"Created On: {createdOn}\n" + $"\u251c {"RF-ID",tabWidth}: {RfId}",
$"Paused: {isPaused}\n\r"; $"\u251c {"Model",tabWidth}: {Enum.GetName(Model)}",
$"\u251c {"Created On",tabWidth}: {CreatedOn}",
$"\u2514 {"Paused",tabWidth}: {IsPaused}")}" +
$"\n\r";
} }
public override bool Equals(object? obj) public override bool Equals(object? obj)
@ -51,11 +52,11 @@ public class OpenShockShocker : Shocker
private bool Equals(OpenShockShocker other) private bool Equals(OpenShockShocker other)
{ {
return id == other.id && rfId == other.rfId && model == other.model && createdOn.Equals(other.createdOn); return ID == other.ID;
} }
public override int GetHashCode() public override int GetHashCode()
{ {
return HashCode.Combine(id, rfId, (int)model, createdOn); return ID.GetHashCode();
} }
} }

View File

@ -5,7 +5,14 @@ namespace CShocker.Shockers;
public class PiShockShocker : Shocker public class PiShockShocker : Shocker
{ {
public string Code; public readonly string Code;
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;
}
public override bool Equals(object? obj) public override bool Equals(object? obj)
{ {
@ -21,11 +28,13 @@ public class PiShockShocker : Shocker
{ {
return Code.GetHashCode(); return Code.GetHashCode();
} }
public PiShockShocker(Api api, string code) : base(api) public override string ToString()
{ {
if (api is not PiShockApi) const int tabWidth = -5;
throw new Exception($"API-Type {api.GetType().FullName} is not usable with Shocker {this.GetType().FullName}"); return $"{string.Join("\n\t",
Code = code; $"\u251c {"Type",tabWidth}: {GetType().Name}",
$"\u2514 {"Code",tabWidth}: {Code}")}" +
$"\n\r";
} }
} }

View File

@ -7,11 +7,9 @@ Library to interact with Shock-Collars that are remotely controllable via ESP32-
```csharp ```csharp
public static void Main(string[] args){ public static void Main(string[] args){
IntensityRange intensityRange = new IntensityRange(30, 50);
DurationRange durationRange = new DurationRange(1000, 2000);
string apiKey = ":)"; string apiKey = ":)";
OpenShockHttp openShockHttp = new (intensityRange, durationRange, apiKey); OpenShockHttp openShockHttp = new (apiKey);
OpenShockShocker shocker1 = openShockHttp.GetShockers().First(); OpenShockShocker shocker1 = openShockHttp.GetShockers().First();
shocker1.Control(ControlAction.Vibrate, 20, 1000); shocker1.Control(ControlAction.Vibrate, 20, 1000);
@ -19,7 +17,7 @@ public static void Main(string[] args){
List<SerialPortInfo> serialPorts = SerialHelper.GetSerialPorts(); List<SerialPortInfo> serialPorts = SerialHelper.GetSerialPorts();
int selectedPort = 1; int selectedPort = 1;
OpenShockSerial openShockSerial = new(intensityRange, durationRange, serialPorts[selectedPort], apiKey); OpenShockSerial openShockSerial = new(serialPorts[selectedPort], apiKey);
OpenShockShocker shocker2 = openShockSerial.GetShockers().First(); OpenShockShocker shocker2 = openShockSerial.GetShockers().First();
shocker2.Control(ControlAction.Vibrate, 20, 1000); shocker2.Control(ControlAction.Vibrate, 20, 1000);
@ -28,10 +26,8 @@ public static void Main(string[] args){
``` ```
## `Shocker.Control` ## `Shocker.Control`
```csharp ```csharp
Control(ControlAction action, int? intensity = null, int? duration = null) Control(ControlAction action, int intensity, int duration)
``` ```
If `intensity` or `duration` are `null`, a random value within the
configured range will be used.
### ControlAction ### ControlAction

View File

@ -3,7 +3,6 @@ using CShocker.Devices.APIs;
using CShocker.Shockers; using CShocker.Shockers;
using GlaxLogger; using GlaxLogger;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
Logger logger = new (LogLevel.Trace); Logger logger = new (LogLevel.Trace);
@ -14,16 +13,22 @@ while(apiKey is null || apiKey.Length < 1)
OpenShockHttp openShockHttp = new (apiKey, logger: logger); OpenShockHttp openShockHttp = new (apiKey, logger: logger);
OpenShockShocker shocker = openShockHttp.GetShockers().First(); foreach (OpenShockShocker shocker in openShockHttp.GetShockers())
shocker.Control(ControlAction.Vibrate, 20, 1000); {
shocker.Control(ControlAction.Vibrate, 20, 1000);
shocker.Control(ControlAction.Vibrate, 20, 1000);
shocker.Control(ControlAction.Vibrate, 20, 1000);
Thread.Sleep(1100);
}
/*
File.WriteAllText("shockers.json", JsonConvert.SerializeObject(shocker)); File.WriteAllText("shockers.json", JsonConvert.SerializeObject(shocker));
OpenShockShocker deserialized = JsonConvert.DeserializeObject<OpenShockShocker>(File.ReadAllText("shockers.json"), new ApiJsonConverter())!; OpenShockShocker deserialized = JsonConvert.DeserializeObject<OpenShockShocker>(File.ReadAllText("shockers.json"), new ApiJsonConverter())!;
Thread.Sleep(1100); //Wait for previous to end Thread.Sleep(1100); //Wait for previous to end
deserialized.Control(ControlAction.Vibrate, 20, 1000); deserialized.Control(ControlAction.Vibrate, 20, 1000);
shocker.Dispose(); shocker.Dispose();
deserialized.Dispose(); deserialized.Dispose();
*/
/* /*
#pragma warning disable CA1416 #pragma warning disable CA1416
@ -53,4 +58,5 @@ shocker.Dispose();
deserialized.Dispose(); deserialized.Dispose();
*/ */
logger.Dispose(); while(!Console.KeyAvailable)
Thread.Sleep(100);

View File

@ -2,9 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<LangVersion>latestmajor</LangVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -12,9 +13,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Reference Include="GlaxLogger"> <PackageReference Include="GlaxLogger" Version="1.0.7.2" />
<HintPath>..\..\GlaxLogger\GlaxLogger\bin\Debug\net7.0\GlaxLogger.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>