diff --git a/CShocker/CShocker.csproj b/CShocker/CShocker.csproj index 15d853d..c7b5052 100644 --- a/CShocker/CShocker.csproj +++ b/CShocker/CShocker.csproj @@ -7,12 +7,14 @@ Glax https://github.com/C9Glax/CShocker git - 1.2.5 + 1.3.0 + + diff --git a/CShocker/Shockers/APIS/OpenShockSerial.cs b/CShocker/Shockers/APIS/OpenShockSerial.cs index db4dbc8..d5af260 100644 --- a/CShocker/Shockers/APIS/OpenShockSerial.cs +++ b/CShocker/Shockers/APIS/OpenShockSerial.cs @@ -1,4 +1,5 @@ -using CShocker.Ranges; +using System.Text.RegularExpressions; +using CShocker.Ranges; using CShocker.Shockers.Abstract; using Microsoft.Extensions.Logging; @@ -6,13 +7,57 @@ namespace CShocker.Shockers.APIS; public class OpenShockSerial : SerialShocker { - public OpenShockSerial(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, ShockerApi.OpenShockSerial, logger) + public readonly Dictionary Model; + private const int BaudRate = 115200; + public OpenShockSerial(Dictionary shockerIds, IntensityRange intensityRange, DurationRange durationRange, SerialPortInfo serialPortI, ILogger? logger = null) : base(shockerIds.Keys.ToList(), intensityRange, durationRange, serialPortI, BaudRate, ShockerApi.OpenShockSerial, logger) { - throw new NotImplementedException(); + this.Model = shockerIds; } protected override void ControlInternal(ControlAction action, string shockerId, int intensity, int duration) { - throw new NotImplementedException(); + string json = "{" + + $"\"model\":\"{Enum.GetName(Model[shockerId])!.ToLower()}\"," + + $"\"id\":{shockerId}," + + $"\"type\":\"{ControlActionToString(action)}\"," + + $"\"intensity\":{intensity}," + + $"\"durationMs\":{duration}" + + "}"; + serialPort.WriteLine(json); + } + + public Dictionary GetShockers() + { + Dictionary ret = new(); + Regex shockerRex = new (@".*FetchDeviceInfo\(\): \[GatewayConnectionManager\] \[[a-z0-9\-]+\] rf=([0-9]{1,5}) model=([0,1])"); + this.Logger?.Log(LogLevel.Debug, "Restart"); + serialPort.WriteLine("restart"); + while (serialPort.ReadLine() is { } line && !line.Contains("Successfully verified auth token")) + { + this.Logger?.Log(LogLevel.Trace, line); + Match match = shockerRex.Match(line); + if (match.Success) + ret.Add(match.Groups[1].Value, Enum.Parse(match.Groups[2].Value)); + } + this.Logger?.Log(LogLevel.Debug, $"Shockers found: \n\t{string.Join("\n\t", ret)}"); + + return ret; + } + + public enum ShockerModel : byte + { + Caixianlin = 0, + Petrainer = 1 + } + + private string ControlActionToString(ControlAction action) + { + return action switch + { + ControlAction.Beep => "sound", + ControlAction.Vibrate => "vibrate", + ControlAction.Shock => "shock", + _ => "stop" + }; } } \ No newline at end of file diff --git a/CShocker/Shockers/Abstract/SerialShocker.cs b/CShocker/Shockers/Abstract/SerialShocker.cs index 01110a2..7edb8c2 100644 --- a/CShocker/Shockers/Abstract/SerialShocker.cs +++ b/CShocker/Shockers/Abstract/SerialShocker.cs @@ -1,11 +1,77 @@ -using CShocker.Ranges; +using System.IO.Ports; +using CShocker.Ranges; using Microsoft.Extensions.Logging; +using System.Management; +using System.Runtime.Versioning; +using Microsoft.Win32; namespace CShocker.Shockers.Abstract; public abstract class SerialShocker : Shocker { - protected SerialShocker(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, ShockerApi apiType, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiType, logger) + public SerialPortInfo SerialPortI; + protected SerialPort serialPort; + + protected SerialShocker(List shockerIds, IntensityRange intensityRange, DurationRange durationRange, SerialPortInfo serialPortI, int baudRate, ShockerApi apiType, ILogger? logger = null) : base(shockerIds, intensityRange, durationRange, apiType, logger) { + this.SerialPortI = serialPortI; + this.serialPort = new SerialPort(serialPortI.PortName, baudRate); + this.serialPort.Open(); + } + + [SupportedOSPlatform("windows")] + public static List GetSerialPorts() + { + List 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 s32_Pos = caption.IndexOf(" (COM"); + if (s32_Pos > 0) // remove COM port from description + caption = caption.Substring(0, s32_Pos); + + ret.Add(new SerialPortInfo( + portName, + caption, + manufacturer, + deviceID)); + } + } + + return ret; + } + + public class SerialPortInfo + { + public readonly string? PortName, Description, Manufacturer, DeviceID; + + public SerialPortInfo(string? portName, string? description, string? manufacturer, string? deviceID) + { + this.PortName = portName; + this.Description = description; + this.Manufacturer = manufacturer; + this.DeviceID = deviceID; + } + + public override string ToString() + { + return + $"PortName: {PortName}\nDescription: {Description}\nManufacturer: {Manufacturer}\nDeviceID: {DeviceID}"; + } } } \ No newline at end of file