Compare commits
59 Commits
09ce5cbedc
...
master
Author | SHA1 | Date | |
---|---|---|---|
34fb4d89bd | |||
828a1a2bfa | |||
0b98638cd5 | |||
74d30ba1b9 | |||
385dba1cf3 | |||
1ff67a5b8b | |||
c55592cd90 | |||
b497117b62 | |||
73c19a3f8f | |||
318598f62a | |||
cf0a7c630d | |||
ce0a287e4e | |||
7693aa8b09 | |||
746e153cb5 | |||
60746d66df | |||
eebf15804a | |||
2c7da0352b | |||
17206bfb8c | |||
4839c12340 | |||
1eed03ac14 | |||
f6e8ddb91d | |||
8d70879c68 | |||
639b813fb6 | |||
171b057d58 | |||
bb8959160a | |||
cbca4fd7de | |||
4e79f40e80 | |||
978b384759 | |||
b56cfa39aa | |||
c14279fbbe | |||
194e54fd9a | |||
8a640491cb | |||
f593efda62 | |||
c603fe0ccb | |||
f939e79e69 | |||
65059f66ed | |||
9596811ae7 | |||
961dcd29d1 | |||
90f804c7c6 | |||
60c1ece41d | |||
ac19e20fb7 | |||
e255caeb64 | |||
8dc8af3792 | |||
dea3268cea | |||
ed4eb644a0 | |||
4b19a9f434 | |||
38f7fe1aa3 | |||
ad93d36d8d | |||
359607879a | |||
a6040f2e5c | |||
1daf9a175e | |||
67bb9c4f9a | |||
2d47aa7492 | |||
d861c95ec3 | |||
cab3536412 | |||
5de7e3c0ce | |||
9bcfd75aa2 | |||
076b6fc271 | |||
610518e5b5 |
12
.idea/.idea.CShocker/.idea/riderPublish.xml
generated
Normal file
12
.idea/.idea.CShocker/.idea/riderPublish.xml
generated
Normal 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>
|
@ -2,6 +2,8 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CShocker", "CShocker\CShocker.csproj", "{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CShocker", "CShocker\CShocker.csproj", "{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestApp", "TestApp\TestApp.csproj", "{3C3879F1-AB10-41C7-BF1A-3C1DA850970B}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -12,5 +14,9 @@ Global
|
|||||||
{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{244585F2-3AD8-4B3A-B6DA-3B0D3EFB745F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{3C3879F1-AB10-41C7-BF1A-3C1DA850970B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{3C3879F1-AB10-41C7-BF1A-3C1DA850970B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3C3879F1-AB10-41C7-BF1A-3C1DA850970B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{3C3879F1-AB10-41C7-BF1A-3C1DA850970B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
5
CShocker.sln.DotSettings
Normal file
5
CShocker.sln.DotSettings
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<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/UserDictionary/Words/=Caixianlin/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Petrainer/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=rftransmit/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Xianlin/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
7
CShocker.sln.DotSettings.user
Normal file
7
CShocker.sln.DotSettings.user
Normal file
@ -0,0 +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">
|
||||||
|
<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"><AssemblyExplorer>
|
||||||
|
<Assembly Path="C:\Users\Glax\RiderProjects\GlaxLogger\GlaxLogger\bin\Debug\net7.0\GlaxLogger.dll" />
|
||||||
|
</AssemblyExplorer></s:String></wpf:ResourceDictionary>
|
@ -1,18 +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>1.2.5</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="9.0.1" />
|
||||||
|
<PackageReference Include="System.Management" Version="9.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
37
CShocker/Devices/APIs/OpenShockHttp.cs
Normal file
37
CShocker/Devices/APIs/OpenShockHttp.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Devices.Additional;
|
||||||
|
using CShocker.Shockers;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.APIs;
|
||||||
|
|
||||||
|
public class OpenShockHttp : OpenShockApi
|
||||||
|
{
|
||||||
|
protected override void ControlInternal(ControlAction action, Shocker 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 = "{" +
|
||||||
|
" \"shocks\": [" +
|
||||||
|
" {" +
|
||||||
|
$" \"id\": \"{openShockShocker.ID}\"," +
|
||||||
|
$" \"type\": \"{Enum.GetName(action)}\"," +
|
||||||
|
$" \"intensity\": {intensity}," +
|
||||||
|
$" \"duration\": {duration}" +
|
||||||
|
" }" +
|
||||||
|
" ]," +
|
||||||
|
" \"customName\": \"CShocker\"" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
ApiHttpClient.MakeAPICall(HttpMethod.Post, $"{Endpoint}/2/shockers/control", json, this.Logger, new ValueTuple<string, string>("OpenShockToken", ApiKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OpenShockHttp(string apiKey, string? endpoint = null, ILogger? logger = null) : base(DeviceApi.OpenShockHttp, apiKey, endpoint??DefaultEndpoint, logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
58
CShocker/Devices/APIs/OpenShockSerial.cs
Normal file
58
CShocker/Devices/APIs/OpenShockSerial.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System.IO.Ports;
|
||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Devices.Additional;
|
||||||
|
using CShocker.Shockers;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.APIs;
|
||||||
|
|
||||||
|
public class OpenShockSerial : OpenShockApi
|
||||||
|
{
|
||||||
|
private const int BaudRate = 115200;
|
||||||
|
public SerialPortInfo SerialPortI;
|
||||||
|
private readonly SerialPort _serialPort;
|
||||||
|
|
||||||
|
public OpenShockSerial(SerialPortInfo serialPortI, string apiKey, string? endpoint = null, ILogger? logger = null) : base(DeviceApi.OpenShockSerial, apiKey, endpoint??DefaultEndpoint, logger)
|
||||||
|
{
|
||||||
|
this.SerialPortI = serialPortI;
|
||||||
|
this._serialPort = new SerialPort(serialPortI.PortName, BaudRate);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
this._serialPort.Open();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.Logger?.Log(LogLevel.Error, e.Message);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ControlInternal(ControlAction action, Shocker 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"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
52
CShocker/Devices/APIs/PiShockHttp.cs
Normal file
52
CShocker/Devices/APIs/PiShockHttp.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Devices.Additional;
|
||||||
|
using CShocker.Shockers;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.APIs;
|
||||||
|
|
||||||
|
public class PiShockHttp : PiShockApi
|
||||||
|
{
|
||||||
|
// ReSharper disable thrice MemberCanBePrivate.Global -> Exposed
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
this.Username = username;
|
||||||
|
this.Endpoint = endpoint;
|
||||||
|
this.ApiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration)
|
||||||
|
{
|
||||||
|
if (shocker is not PiShockShocker piShockShocker)
|
||||||
|
{
|
||||||
|
this.Logger?.Log(LogLevel.Warning, $"Shocker {shocker} is not {typeof(OpenShockShocker).FullName}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = "{" +
|
||||||
|
$"\"Username\":\"{Username}\"," +
|
||||||
|
"\"Name\":\"CShocker\"," +
|
||||||
|
$"\"Code\":\"{piShockShocker.Code}\"," +
|
||||||
|
$"\"Intensity\":\"{intensity}\"," +
|
||||||
|
$"\"Duration\":\"{duration / 1000}\"," + //duration is in seconds no ms
|
||||||
|
$"\"Apikey\":\"{ApiKey}\"," +
|
||||||
|
$"\"Op\":\"{ControlActionToByte(action)}\"" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
ApiHttpClient.MakeAPICall(HttpMethod.Post, $"{Endpoint}", json, this.Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte ControlActionToByte(ControlAction action)
|
||||||
|
{
|
||||||
|
return action switch
|
||||||
|
{
|
||||||
|
ControlAction.Beep => 2,
|
||||||
|
ControlAction.Vibrate => 1,
|
||||||
|
ControlAction.Shock => 0,
|
||||||
|
_ => 2
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
47
CShocker/Devices/APIs/PiShockSerial.cs
Normal file
47
CShocker/Devices/APIs/PiShockSerial.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.IO.Ports;
|
||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Devices.Additional;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.APIs;
|
||||||
|
|
||||||
|
public class PiShockSerial : PiShockApi
|
||||||
|
{
|
||||||
|
private const int BaudRate = 115200;
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global -> Exposed
|
||||||
|
public readonly SerialPortInfo SerialPortI;
|
||||||
|
private readonly SerialPort _serialPort;
|
||||||
|
|
||||||
|
public PiShockSerial(DeviceApi apiType, SerialPortInfo serialPortI, ILogger? logger = null) : base(apiType, logger)
|
||||||
|
{
|
||||||
|
this.SerialPortI = serialPortI;
|
||||||
|
this._serialPort = new SerialPort(this.SerialPortI.PortName, BaudRate);
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration)
|
||||||
|
{
|
||||||
|
string json = "{" +
|
||||||
|
"\"cmd\": \"operate\"," +
|
||||||
|
"\"value\":{" +
|
||||||
|
$"\"op\": \"{ControlActionToOp(action)}\"," +
|
||||||
|
$"\"duration\": {duration}," +
|
||||||
|
$"\"intensity\": {intensity}," +
|
||||||
|
$"\"id\": " +
|
||||||
|
"}" +
|
||||||
|
"}";
|
||||||
|
_serialPort.WriteLine(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ControlActionToOp(ControlAction action)
|
||||||
|
{
|
||||||
|
return action switch
|
||||||
|
{
|
||||||
|
ControlAction.Beep => "",
|
||||||
|
ControlAction.Vibrate => "vibrate",
|
||||||
|
ControlAction.Shock => "",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
100
CShocker/Devices/Abstract/Api.cs
Normal file
100
CShocker/Devices/Abstract/Api.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
using CShocker.Devices.Additional;
|
||||||
|
using CShocker.Ranges;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.Abstract;
|
||||||
|
|
||||||
|
public abstract class Api : IDisposable
|
||||||
|
{
|
||||||
|
// ReSharper disable 4 times MemberCanBePrivate.Global -> Exposed
|
||||||
|
protected ILogger? Logger;
|
||||||
|
public readonly DeviceApi ApiType;
|
||||||
|
private Queue<DateTime> order = new();
|
||||||
|
private Dictionary<DateTime, Task> tasks = new();
|
||||||
|
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
|
||||||
|
private const short CommandDelay = 50;
|
||||||
|
internal readonly IntegerRange ValidIntensityRange, ValidDurationRange;
|
||||||
|
|
||||||
|
internal void Control(ControlAction action, int intensity, int duration, params Shocker[] shockers)
|
||||||
|
{
|
||||||
|
bool enqueueItem = true;
|
||||||
|
if (!ValidIntensityRange.IsValueWithinLimits(intensity))
|
||||||
|
{
|
||||||
|
this.Logger?.Log(LogLevel.Information, $"Value not within allowed {nameof(intensity)}-Range ({ValidIntensityRange.RangeString()}): {intensity}");
|
||||||
|
enqueueItem = false;
|
||||||
|
}
|
||||||
|
if (!ValidDurationRange.IsValueWithinLimits(duration))
|
||||||
|
{
|
||||||
|
this.Logger?.Log(LogLevel.Information, $"Value not within allowed {nameof(duration)}-Range ({ValidIntensityRange.RangeString()}): {duration}");
|
||||||
|
enqueueItem = false;
|
||||||
|
}
|
||||||
|
if (!enqueueItem)
|
||||||
|
{
|
||||||
|
this.Logger?.Log(LogLevel.Information, "Doing nothing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
foreach (Shocker shocker in shockers)
|
||||||
|
{
|
||||||
|
this.Logger?.Log(LogLevel.Debug, $"Enqueueing {action} Intensity: {intensity} Duration: {duration}\nShocker:\n{shocker}");
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void ControlInternal(ControlAction action, Shocker shocker, int intensity, int duration);
|
||||||
|
|
||||||
|
protected Api(DeviceApi apiType, IntegerRange validIntensityRange, IntegerRange validDurationRange, ILogger? logger = null)
|
||||||
|
{
|
||||||
|
this.ApiType = apiType;
|
||||||
|
this.Logger = logger;
|
||||||
|
this.ValidIntensityRange = validIntensityRange;
|
||||||
|
this.ValidDurationRange = validDurationRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ExecuteTask(DateTime when, ValueTuple<ControlAction, Shocker, int, int> tuple)
|
||||||
|
{
|
||||||
|
while (order.First() != when)
|
||||||
|
Thread.Sleep(CommandDelay);
|
||||||
|
this.Logger?.Log(LogLevel.Information, $"Executing: {Enum.GetName(tuple.Item1)} Intensity: {tuple.Item3} Duration: {tuple.Item4}\nShocker:\n{tuple.Item2}");
|
||||||
|
ControlInternal(tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4);
|
||||||
|
Thread.Sleep(tuple.Item4);
|
||||||
|
tasks.Remove(when);
|
||||||
|
order.Dequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetLogger(ILogger? logger)
|
||||||
|
{
|
||||||
|
this.Logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"ShockerType: {Enum.GetName(typeof(DeviceApi), this.ApiType)}\n\r";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is Api d && Equals(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool Equals(Api other)
|
||||||
|
{
|
||||||
|
return ApiType == other.ApiType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return ApiType.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach ((DateTime when, Task? task) in tasks)
|
||||||
|
task?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
79
CShocker/Devices/Abstract/OpenShockApi.cs
Normal file
79
CShocker/Devices/Abstract/OpenShockApi.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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 OpenShockApi : Api
|
||||||
|
{
|
||||||
|
// ReSharper disable twice MemberCanBeProtected.Global -> Exposed
|
||||||
|
public string Endpoint { get; init; }
|
||||||
|
public string ApiKey { get; init; }
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
this.Endpoint = endpoint;
|
||||||
|
this.ApiKey = apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is OpenShockApi osd && Equals(osd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(OpenShockApi other)
|
||||||
|
{
|
||||||
|
return base.Equals(other) && Endpoint.Equals(other.Endpoint) && ApiKey.Equals(other.ApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Endpoint, ApiKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<OpenShockShocker> GetShockers()
|
||||||
|
{
|
||||||
|
return GetShockers(this.ApiKey, this, this.Endpoint, this.Logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once MemberCanBePrivate.Global
|
||||||
|
public static IEnumerable<OpenShockShocker> GetShockers(string apiKey, OpenShockApi api, string apiEndpoint = DefaultEndpoint, ILogger? logger = null)
|
||||||
|
{
|
||||||
|
List<OpenShockShocker> shockers = new();
|
||||||
|
|
||||||
|
HttpResponseMessage ownResponse = ApiHttpClient.MakeAPICall(HttpMethod.Get, $"{apiEndpoint}/1/shockers/own", null, logger, new ValueTuple<string, string>("OpenShockToken", apiKey));
|
||||||
|
if (!ownResponse.IsSuccessStatusCode)
|
||||||
|
return shockers;
|
||||||
|
|
||||||
|
StreamReader ownShockerStreamReader = new(ownResponse.Content.ReadAsStream());
|
||||||
|
string ownShockerJson = ownShockerStreamReader.ReadToEnd();
|
||||||
|
logger?.Log(LogLevel.Debug,ownShockerJson);
|
||||||
|
JObject ownShockerListJObj = JObject.Parse(ownShockerJson);
|
||||||
|
shockers.AddRange(ownShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t =>
|
||||||
|
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>())));
|
||||||
|
|
||||||
|
HttpResponseMessage sharedResponse = ApiHttpClient.MakeAPICall(HttpMethod.Get, $"{apiEndpoint}/1/shockers/shared", null, logger, new ValueTuple<string, string>("OpenShockToken", apiKey));
|
||||||
|
if (!sharedResponse.IsSuccessStatusCode)
|
||||||
|
return shockers;
|
||||||
|
|
||||||
|
StreamReader sharedShockerStreamReader = new(sharedResponse.Content.ReadAsStream());
|
||||||
|
string sharedShockerJson = sharedShockerStreamReader.ReadToEnd();
|
||||||
|
logger?.Log(LogLevel.Debug, sharedShockerJson);
|
||||||
|
JObject sharedShockerListJObj = JObject.Parse(sharedShockerJson);
|
||||||
|
shockers.AddRange(sharedShockerListJObj.SelectTokens("$.data..shockers[*]").Select(t =>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
12
CShocker/Devices/Abstract/PiShockApi.cs
Normal file
12
CShocker/Devices/Abstract/PiShockApi.cs
Normal 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(DeviceApi apiType, ILogger? logger = null) : base(apiType, new IntegerRange(0, 100), new IntegerRange(1000, 15000), logger)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
26
CShocker/Devices/Abstract/SerialPortInfo.cs
Normal file
26
CShocker/Devices/Abstract/SerialPortInfo.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
namespace CShocker.Devices.Abstract;
|
||||||
|
|
||||||
|
public readonly struct SerialPortInfo
|
||||||
|
{
|
||||||
|
// ReSharper disable thrice MemberCanBePrivate.Global -> Exposed
|
||||||
|
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 $"{string.Join("\n\t",
|
||||||
|
$"{GetType().Name}",
|
||||||
|
$"PortName: {PortName}",
|
||||||
|
$"Description: {Description}",
|
||||||
|
$"Manufacturer: {Manufacturer}",
|
||||||
|
$"DeviceID: {DeviceID}")}" +
|
||||||
|
$"\n\r";
|
||||||
|
}
|
||||||
|
}
|
66
CShocker/Devices/Additional/ApiHttpClient.cs
Normal file
66
CShocker/Devices/Additional/ApiHttpClient.cs
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.Additional;
|
||||||
|
|
||||||
|
public static class ApiHttpClient
|
||||||
|
{
|
||||||
|
private static readonly ProductInfoHeaderValue UserAgent = GetUserAgent();
|
||||||
|
|
||||||
|
private static ProductInfoHeaderValue GetUserAgent()
|
||||||
|
{
|
||||||
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
|
FileVersionInfo fvi;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
Headers =
|
||||||
|
{
|
||||||
|
UserAgent = { UserAgent },
|
||||||
|
Accept = { new MediaTypeWithQualityHeaderValue("application/json") },
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (jsonContent is not null && jsonContent.Length > 0)
|
||||||
|
request.Content = new ByteArrayContent(Encoding.UTF8.GetBytes(jsonContent))
|
||||||
|
{
|
||||||
|
Headers = { ContentType = MediaTypeHeaderValue.Parse("application/json") }
|
||||||
|
};
|
||||||
|
foreach ((string, string) customHeader in customHeaders)
|
||||||
|
request.Headers.Add(customHeader.Item1, customHeader.Item2);
|
||||||
|
logger?.Log(LogLevel.Debug, string.Join("\n\t",
|
||||||
|
"Request:",
|
||||||
|
$"\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();
|
||||||
|
HttpResponseMessage response = httpClient.Send(request);
|
||||||
|
logger?.Log(!response.IsSuccessStatusCode ? LogLevel.Error : LogLevel.Debug, string.Join("\n\t",
|
||||||
|
"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();
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
44
CShocker/Devices/Additional/ApiJsonConverter.cs
Normal file
44
CShocker/Devices/Additional/ApiJsonConverter.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Devices.APIs;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.Additional;
|
||||||
|
|
||||||
|
public class ApiJsonConverter : JsonConverter
|
||||||
|
{
|
||||||
|
public override bool CanConvert(Type objectType)
|
||||||
|
{
|
||||||
|
return (objectType == typeof(Api));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
JObject jo = JObject.Load(reader);
|
||||||
|
DeviceApi apiType = (DeviceApi)jo.SelectToken("ApiType")!.Value<byte>();
|
||||||
|
|
||||||
|
switch (apiType)
|
||||||
|
{
|
||||||
|
case DeviceApi.OpenShockHttp:
|
||||||
|
return jo.ToObject<OpenShockHttp>()!;
|
||||||
|
case DeviceApi.OpenShockSerial:
|
||||||
|
return jo.ToObject<OpenShockSerial>()!;
|
||||||
|
case DeviceApi.PiShockHttp:
|
||||||
|
return jo.ToObject<PiShockHttp>()!;
|
||||||
|
case DeviceApi.PiShockSerial:
|
||||||
|
return jo.ToObject<PiShockSerial>()!;
|
||||||
|
default:
|
||||||
|
throw new Exception();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Don't call this
|
||||||
|
/// </summary>
|
||||||
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
throw new Exception("Dont call this");
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
namespace CShocker.Shockers;
|
namespace CShocker.Devices.Additional;
|
||||||
|
|
||||||
public enum ControlAction
|
public enum ControlAction
|
||||||
{
|
{
|
||||||
Beep,
|
Beep,
|
||||||
Vibrate,
|
Vibrate,
|
||||||
Shock,
|
Shock,
|
||||||
Nothing
|
Stop
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace CShocker.Shockers.Abstract;
|
namespace CShocker.Devices.Additional;
|
||||||
|
|
||||||
public enum ShockerApi : byte
|
public enum DeviceApi : byte
|
||||||
{
|
{
|
||||||
OpenShockHttp = 0,
|
OpenShockHttp = 0,
|
||||||
OpenShockSerial = 1,
|
OpenShockSerial = 1,
|
47
CShocker/Devices/Additional/SerialHelper.cs
Normal file
47
CShocker/Devices/Additional/SerialHelper.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using System.Management;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace CShocker.Devices.Additional;
|
||||||
|
|
||||||
|
public static class SerialHelper
|
||||||
|
{
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
public static List<SerialPortInfo> GetSerialPorts()
|
||||||
|
{
|
||||||
|
List<SerialPortInfo> ret = new();
|
||||||
|
using (ManagementClass entity = new("Win32_PnPEntity"))
|
||||||
|
{
|
||||||
|
// ReSharper disable once InconsistentNaming
|
||||||
|
const string CUR_CTRL = "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\";
|
||||||
|
|
||||||
|
foreach (ManagementObject instance in entity.GetInstances())
|
||||||
|
{
|
||||||
|
object oGuid;
|
||||||
|
oGuid = instance.GetPropertyValue("ClassGuid");
|
||||||
|
if (oGuid == null || oGuid.ToString()?.ToUpper().Equals("{4D36E978-E325-11CE-BFC1-08002BE10318}") is false)
|
||||||
|
continue; // Skip all devices except device class "PORTS"
|
||||||
|
|
||||||
|
string? caption = instance.GetPropertyValue("Caption")?.ToString();
|
||||||
|
string? manufacturer = instance.GetPropertyValue("Manufacturer")?.ToString();
|
||||||
|
string? deviceID = instance.GetPropertyValue("PnpDeviceID")?.ToString();
|
||||||
|
string regEnum = CUR_CTRL + "Enum\\" + deviceID + "\\Device Parameters";
|
||||||
|
string? portName = Registry.GetValue(regEnum, "PortName", "")?.ToString();
|
||||||
|
|
||||||
|
int? s32Pos = caption?.IndexOf(" (COM");
|
||||||
|
if (s32Pos > 0) // remove COM port from description
|
||||||
|
caption = caption?.Substring(0, (int)s32Pos);
|
||||||
|
|
||||||
|
ret.Add(new SerialPortInfo(
|
||||||
|
portName,
|
||||||
|
caption,
|
||||||
|
manufacturer,
|
||||||
|
deviceID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
namespace CShocker.Ranges;
|
|
||||||
|
|
||||||
public class DurationRange : RandomIntegerRange
|
|
||||||
{
|
|
||||||
public DurationRange(short min, short max) : base(min ,max , 0, 30000)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
28
CShocker/Ranges/IntegerRange.cs
Normal file
28
CShocker/Ranges/IntegerRange.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
namespace CShocker.Ranges;
|
||||||
|
|
||||||
|
public readonly struct IntegerRange
|
||||||
|
{
|
||||||
|
// ReSharper disable twice MemberCanBePrivate.Global -> Exposed
|
||||||
|
public readonly int Min, Max;
|
||||||
|
|
||||||
|
public IntegerRange(int min, int max)
|
||||||
|
{
|
||||||
|
this.Min = min;
|
||||||
|
this.Max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsValueWithinLimits(int value)
|
||||||
|
{
|
||||||
|
return value >= this.Min && value <= this.Max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int RandomValueWithinLimits()
|
||||||
|
{
|
||||||
|
return Random.Shared.Next(this.Min, this.Max);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal string RangeString()
|
||||||
|
{
|
||||||
|
return $"{this.Min}-{this.Max}";
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
namespace CShocker.Ranges;
|
|
||||||
|
|
||||||
public class IntensityRange : RandomIntegerRange
|
|
||||||
{
|
|
||||||
public IntensityRange(short min, short max) : base(min , max, 0, 100)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
namespace CShocker.Ranges;
|
|
||||||
|
|
||||||
public abstract class RandomIntegerRange
|
|
||||||
{
|
|
||||||
public short Min, Max;
|
|
||||||
internal RandomIntegerRange(short min, short max, short minLimit, short maxLimit)
|
|
||||||
{
|
|
||||||
if (max - min < 0)
|
|
||||||
throw new ArgumentException("Min has to be less or equal Max");
|
|
||||||
if (min < minLimit || min > maxLimit)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(min), $"Min has to be withing Range {minLimit}-{maxLimit}");
|
|
||||||
if (max < minLimit || max > maxLimit)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(max), $"Max has to be withing Range {minLimit}-{maxLimit}");
|
|
||||||
this.Min = min;
|
|
||||||
this.Max = max;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int GetRandomRangeValue()
|
|
||||||
{
|
|
||||||
return Random.Shared.Next(this.Min, this.Max);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"Min: {Min} Max: {Max}";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,96 +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<string> 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);
|
|
||||||
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<string> deviceIds = new();
|
|
||||||
deviceIds.AddRange(deviceListJObj["data"]!.Children()["id"].Values<string>()!);
|
|
||||||
|
|
||||||
List<string> 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);
|
|
||||||
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);
|
|
||||||
shockerIds.AddRange(shockerListJObj["data"]!.Children()["id"].Values<string>()!);
|
|
||||||
|
|
||||||
}
|
|
||||||
return shockerIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
|
|
||||||
{
|
|
||||||
HttpRequestMessage request = new (HttpMethod.Post, $"{Endpoint}/1/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);
|
|
||||||
HttpResponseMessage response = HttpClient.Send(request);
|
|
||||||
this.Logger?.Log(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<string> 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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using CShocker.Ranges;
|
|
||||||
using CShocker.Shockers.Abstract;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CShocker.Shockers.APIS;
|
|
||||||
|
|
||||||
public class OpenShockSerial : SerialShocker
|
|
||||||
{
|
|
||||||
public OpenShockSerial(List<string> shockerIds, IntensityRange intensityRange, DurationRange durationRange, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, ShockerApi.OpenShockSerial, logger)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +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
|
|
||||||
{
|
|
||||||
public String Username, ShareCode;
|
|
||||||
public PiShockHttp(List<string> 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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
using CShocker.Ranges;
|
|
||||||
using CShocker.Shockers.Abstract;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CShocker.Shockers.APIS;
|
|
||||||
|
|
||||||
public class PiShockSerial : SerialShocker
|
|
||||||
{
|
|
||||||
public PiShockSerial(List<string> shockerIds, IntensityRange intensityRange, DurationRange durationRange, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, ShockerApi.PiShockSerial, logger)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
using CShocker.Ranges;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CShocker.Shockers.Abstract;
|
|
||||||
|
|
||||||
public abstract class HttpShocker : Shocker
|
|
||||||
{
|
|
||||||
protected readonly HttpClient HttpClient = new();
|
|
||||||
public string Endpoint { get; init; }
|
|
||||||
public string ApiKey { get; init; }
|
|
||||||
|
|
||||||
protected HttpShocker(List<string> 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}";
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
using CShocker.Ranges;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CShocker.Shockers.Abstract;
|
|
||||||
|
|
||||||
public abstract class SerialShocker : Shocker
|
|
||||||
{
|
|
||||||
protected SerialShocker(List<string> shockerIds, IntensityRange intensityRange, DurationRange durationRange, ShockerApi apiType, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiType, logger)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,52 +1,25 @@
|
|||||||
using System.Reflection.Metadata;
|
using CShocker.Devices.Abstract;
|
||||||
using CShocker.Ranges;
|
using CShocker.Devices.Additional;
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace CShocker.Shockers.Abstract;
|
namespace CShocker.Shockers.Abstract;
|
||||||
|
|
||||||
public abstract class Shocker
|
public abstract class Shocker : IDisposable
|
||||||
{
|
{
|
||||||
public readonly List<string> ShockerIds;
|
// ReSharper disable once MemberCanBePrivate.Global -> Exposed
|
||||||
public readonly IntensityRange IntensityRange;
|
public Api Api { get; }
|
||||||
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<string> shockerIds, IntensityRange intensityRange, DurationRange durationRange, ShockerApi apiType, ILogger? logger = null)
|
internal Shocker(Api api)
|
||||||
{
|
{
|
||||||
this.ShockerIds = shockerIds;
|
this.Api = api;
|
||||||
this.IntensityRange = intensityRange;
|
|
||||||
this.DurationRange = durationRange;
|
|
||||||
this.ApiType = apiType;
|
|
||||||
this.Logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetLogger(ILogger? logger)
|
public void Control(ControlAction action, int intensity, int duration)
|
||||||
{
|
{
|
||||||
this.Logger = logger;
|
this.Api.Control(action, intensity, duration, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
return $"ShockerType: {Enum.GetName(typeof(ShockerApi), this.ApiType)}\n" +
|
Api.Dispose();
|
||||||
$"Shocker-IDs: {string.Join(", ", this.ShockerIds)}\n" +
|
|
||||||
$"IntensityRange: {IntensityRange}\n" +
|
|
||||||
$"DurationRange: {DurationRange}";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
36
CShocker/Shockers/Additional/ShockerJsonConverter.cs
Normal file
36
CShocker/Shockers/Additional/ShockerJsonConverter.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
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(Shocker));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
JObject jo = JObject.Load(reader);
|
||||||
|
if (jo.ContainsKey("Model")) //OpenShockShocker
|
||||||
|
{
|
||||||
|
return jo.ToObject<OpenShockShocker>(serializer)!;
|
||||||
|
}
|
||||||
|
else //PiShockShocker
|
||||||
|
{
|
||||||
|
return jo.ToObject<PiShockShocker>(serializer)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Don't call this
|
||||||
|
/// </summary>
|
||||||
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException("Dont call this");
|
||||||
|
}
|
||||||
|
}
|
62
CShocker/Shockers/OpenShockShocker.cs
Normal file
62
CShocker/Shockers/OpenShockShocker.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
|
||||||
|
namespace CShocker.Shockers;
|
||||||
|
|
||||||
|
public class OpenShockShocker : Shocker
|
||||||
|
{
|
||||||
|
// ReSharper disable thrice MemberCanBePrivate.Global -> Exposed
|
||||||
|
public readonly string Name, ID;
|
||||||
|
public readonly short RfId;
|
||||||
|
public readonly OpenShockModel Model;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
Petrainer = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
const int tabWidth = -12;
|
||||||
|
return $"\t{string.Join("\n\t",
|
||||||
|
$"\u251c {"Type",tabWidth}: {GetType().Name}",
|
||||||
|
$"\u251c {"Name",tabWidth}: {Name}",
|
||||||
|
$"\u251c {"ID",tabWidth}: {ID}",
|
||||||
|
$"\u251c {"RF-ID",tabWidth}: {RfId}",
|
||||||
|
$"\u251c {"Model",tabWidth}: {Enum.GetName(Model)}",
|
||||||
|
$"\u251c {"Created On",tabWidth}: {CreatedOn}",
|
||||||
|
$"\u2514 {"Paused",tabWidth}: {IsPaused}")}" +
|
||||||
|
$"\n\r";
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is OpenShockShocker oss && Equals(oss);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(OpenShockShocker other)
|
||||||
|
{
|
||||||
|
return ID == other.ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return ID.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
40
CShocker/Shockers/PiShockShocker.cs
Normal file
40
CShocker/Shockers/PiShockShocker.cs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
using CShocker.Devices.Abstract;
|
||||||
|
using CShocker.Shockers.Abstract;
|
||||||
|
|
||||||
|
namespace CShocker.Shockers;
|
||||||
|
|
||||||
|
public class PiShockShocker : Shocker
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return obj is PiShockShocker pss && Equals(pss);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Equals(PiShockShocker other)
|
||||||
|
{
|
||||||
|
return Code == other.Code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Code.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
const int tabWidth = -5;
|
||||||
|
return $"{string.Join("\n\t",
|
||||||
|
$"\u251c {"Type",tabWidth}: {GetType().Name}",
|
||||||
|
$"\u2514 {"Code",tabWidth}: {Code}")}" +
|
||||||
|
$"\n\r";
|
||||||
|
}
|
||||||
|
}
|
@ -1,58 +0,0 @@
|
|||||||
using CShocker.Ranges;
|
|
||||||
using CShocker.Shockers.Abstract;
|
|
||||||
using CShocker.Shockers.APIS;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace CShocker.Shockers;
|
|
||||||
|
|
||||||
public class ShockerJsonConverter : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert(Type objectType)
|
|
||||||
{
|
|
||||||
return (objectType == typeof(Shocker));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override object ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
JObject jo = JObject.Load(reader);
|
|
||||||
ShockerApi? apiType = (ShockerApi?)jo.SelectToken("ApiType")?.Value<byte>();
|
|
||||||
|
|
||||||
switch (apiType)
|
|
||||||
{
|
|
||||||
case ShockerApi.OpenShockHttp:
|
|
||||||
return new OpenShockHttp(
|
|
||||||
jo.SelectToken("ShockerIds")!.ToObject<List<string>>()!,
|
|
||||||
jo.SelectToken("IntensityRange")!.ToObject<IntensityRange>()!,
|
|
||||||
jo.SelectToken("DurationRange")!.ToObject<DurationRange>()!,
|
|
||||||
jo.SelectToken("ApiKey")!.Value<string>()!,
|
|
||||||
jo.SelectToken("Endpoint")!.Value<string>()!
|
|
||||||
);
|
|
||||||
case ShockerApi.OpenShockSerial:
|
|
||||||
case ShockerApi.PiShockHttp:
|
|
||||||
return new PiShockHttp(
|
|
||||||
jo.SelectToken("ShockerIds")!.ToObject<List<string>>()!,
|
|
||||||
jo.SelectToken("IntensityRange")!.ToObject<IntensityRange>()!,
|
|
||||||
jo.SelectToken("DurationRange")!.ToObject<DurationRange>()!,
|
|
||||||
jo.SelectToken("ApiKey")!.Value<string>()!,
|
|
||||||
jo.SelectToken("Username")!.Value<string>()!,
|
|
||||||
jo.SelectToken("ShareCode")!.Value<string>()!,
|
|
||||||
jo.SelectToken("Endpoint")!.Value<string>()!
|
|
||||||
);
|
|
||||||
case ShockerApi.PiShockSerial:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
default:
|
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite => false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Don't call this
|
|
||||||
/// </summary>
|
|
||||||
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
throw new Exception("Dont call this");
|
|
||||||
}
|
|
||||||
}
|
|
48
README.md
48
README.md
@ -1,51 +1,42 @@
|
|||||||
Library to interact with Shock-Collars that are remotely controllable via ESP32-Boards.
|
Library to interact with Shock-Collars that are remotely controllable via ESP32-Boards.
|
||||||
|
|
||||||
[](https://github.com/C9Glax/CShocker)
|
[](https://github.com/C9Glax/CShocker)
|
||||||
[](https://shields.io/badges/nu-get-version)
|
[](https://www.nuget.org/packages/CShocker)
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
public static void Main(string[] args){
|
public static void Main(string[] args){
|
||||||
List<string> shockerIds = new();
|
|
||||||
IntensityRange intensityRange = new IntensityRange(30, 50);
|
|
||||||
DurationRange durationRange = new DurationRange(1000, 2000);
|
|
||||||
string apiKey = ":)";
|
string apiKey = ":)";
|
||||||
OpenShockHttp openShockHttp = new OpenShockHttp(shockerIds, intensityRange, durationRange, apiKey);
|
|
||||||
openShockHttp.ShockerIds.AddRange(openShockHttp.GetShockers());
|
|
||||||
|
|
||||||
string username = "username";
|
OpenShockHttp openShockHttp = new (apiKey);
|
||||||
string shareCode = "sharecode";
|
OpenShockShocker shocker1 = openShockHttp.GetShockers().First();
|
||||||
PiShockHttp piShockHttp = new PiShockHttp(shockerIds, intensityRange, durationRange, apiKey, username, shareCode, apiUri);
|
shocker1.Control(ControlAction.Vibrate, 20, 1000);
|
||||||
|
|
||||||
ControlAction action = ControlAction.Shock;
|
shocker1.Dispose();
|
||||||
openShockHttp.Control(action, shockerIds.First(), 20, 1000);
|
|
||||||
piShockHttp.Control(action);
|
List<SerialPortInfo> serialPorts = SerialHelper.GetSerialPorts();
|
||||||
|
int selectedPort = 1;
|
||||||
|
OpenShockSerial openShockSerial = new(serialPorts[selectedPort], apiKey);
|
||||||
|
OpenShockShocker shocker2 = openShockSerial.GetShockers().First();
|
||||||
|
shocker2.Control(ControlAction.Vibrate, 20, 1000);
|
||||||
|
|
||||||
|
shocker2.Dispose();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
## `Shocker.Control`
|
## `Shocker.Control`
|
||||||
```csharp
|
```csharp
|
||||||
Control(ControlAction action, string? shockerId = null, int? intensity = null, int? duration = null)
|
Control(ControlAction action, int intensity, int duration)
|
||||||
```
|
```
|
||||||
If `shockerId` is `null`, all IDs will be used. If `intensity` or `duration` are `null`, a random value within the
|
|
||||||
configured range will be used.
|
|
||||||
|
|
||||||
|
|
||||||
### ControlAction
|
### ControlAction
|
||||||
From [here](https://github.com/C9Glax/CShocker/blob/master/CShocker/Shockers/ControlAction.cs)
|
From [here](https://github.com/C9Glax/CShocker/blob/master/CShocker/Devices/Additional/ControlActionEnum.cs)
|
||||||
|
|
||||||
## Variables
|
## Variables
|
||||||
|
|
||||||
### ApiKey
|
### ApiKey
|
||||||
- For OpenShock (HTTP) get token [here](https://shocklink.net/#/dashboard/tokens)
|
- For OpenShock (HTTP) get token [here](https://shocklink.net/#/dashboard/tokens)
|
||||||
- For PiShock (HTTP) get information [here](https://apidocs.pishock.com/#header-authenticating)
|
|
||||||
|
|
||||||
### ShockerIds
|
|
||||||
List of Shocker-Ids, comma seperated.
|
|
||||||
|
|
||||||
`[ "ID-1-asdasd", "ID-2-fghfgh" ]`
|
|
||||||
|
|
||||||
OpenShockHttp also can retrieve IDs automatically.
|
|
||||||
|
|
||||||
### Intensity Range
|
### Intensity Range
|
||||||
in percent
|
in percent
|
||||||
@ -57,8 +48,9 @@ in ms
|
|||||||
- `0-30000` OpenShock
|
- `0-30000` OpenShock
|
||||||
- `0-15000` PiShock
|
- `0-15000` PiShock
|
||||||
|
|
||||||
### Username (PiShockHttp only)
|
## Future
|
||||||
For PiShock (HTTP) get information [here](https://apidocs.pishock.com/#header-authenticating)
|
### ~~Username (PiShockHttp only)~~
|
||||||
|
~~For PiShock (HTTP) get information [here](https://apidocs.pishock.com/#header-authenticating)~~
|
||||||
|
|
||||||
### Sharecode (PiShockHttp only)
|
### ~~Sharecode (PiShockHttp only)~~
|
||||||
For PiShock (HTTP) get information [here](https://apidocs.pishock.com/#header-authenticating)
|
~~For PiShock (HTTP) get information [here](https://apidocs.pishock.com/#header-authenticating)~~
|
||||||
|
62
TestApp/Program.cs
Normal file
62
TestApp/Program.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using CShocker.Devices.Additional;
|
||||||
|
using CShocker.Devices.APIs;
|
||||||
|
using CShocker.Shockers;
|
||||||
|
using GlaxLogger;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
Logger logger = new (LogLevel.Trace);
|
||||||
|
|
||||||
|
Console.WriteLine("OpenShock API Key:");
|
||||||
|
string? apiKey = Console.ReadLine();
|
||||||
|
while(apiKey is null || apiKey.Length < 1)
|
||||||
|
apiKey = Console.ReadLine();
|
||||||
|
|
||||||
|
|
||||||
|
OpenShockHttp openShockHttp = new (apiKey, logger: logger);
|
||||||
|
foreach (OpenShockShocker shocker in openShockHttp.GetShockers())
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
OpenShockShocker deserialized = JsonConvert.DeserializeObject<OpenShockShocker>(File.ReadAllText("shockers.json"), new ApiJsonConverter())!;
|
||||||
|
Thread.Sleep(1100); //Wait for previous to end
|
||||||
|
deserialized.Control(ControlAction.Vibrate, 20, 1000);
|
||||||
|
shocker.Dispose();
|
||||||
|
deserialized.Dispose();
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
#pragma warning disable CA1416
|
||||||
|
List<SerialPortInfo> serialPorts = SerialHelper.GetSerialPorts();
|
||||||
|
|
||||||
|
if (serialPorts.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for(int i = 0; i < serialPorts.Count; i++)
|
||||||
|
Console.WriteLine($"{i}) {serialPorts[i]}");
|
||||||
|
|
||||||
|
Console.WriteLine($"Select Serial Port [0-{serialPorts.Count-1}]:");
|
||||||
|
string? selectedPortStr = Console.ReadLine();
|
||||||
|
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 openShockSerial = new(serialPorts[selectedPort], apiKey, logger: logger);
|
||||||
|
OpenShockShocker shocker = openShockSerial.GetShockers().First();
|
||||||
|
shocker.Control(ControlAction.Vibrate, 20, 1000);
|
||||||
|
File.WriteAllText("shockers.json", JsonConvert.SerializeObject(shocker));
|
||||||
|
OpenShockShocker deserialized = JsonConvert.DeserializeObject<OpenShockShocker>(File.ReadAllText("shockers.json"), new ApiJsonConverter())!;
|
||||||
|
shocker.Dispose();
|
||||||
|
deserialized.Dispose();
|
||||||
|
*/
|
||||||
|
|
||||||
|
while(!Console.KeyAvailable)
|
||||||
|
Thread.Sleep(100);
|
19
TestApp/TestApp.csproj
Normal file
19
TestApp/TestApp.csproj
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||||
|
<LangVersion>latestmajor</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\CShocker\CShocker.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="GlaxLogger" Version="1.0.7.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Reference in New Issue
Block a user