From 8f89382a53d247b77008f7300493ff9a46349cb8 Mon Sep 17 00:00:00 2001 From: TTLC198 <41226242+TTLC198@users.noreply.github.com> Date: Sat, 11 Nov 2023 19:51:00 +0300 Subject: [PATCH] Added a class for obtaining data about connected devices --- HSMonitor/Utils/Usb/Serial/Serial.cs | 101 ++++++ HSMonitor/Utils/Usb/Win32DeviceMgmt.cs | 426 +++++++++++++++++++++++++ 2 files changed, 527 insertions(+) create mode 100644 HSMonitor/Utils/Usb/Serial/Serial.cs create mode 100644 HSMonitor/Utils/Usb/Win32DeviceMgmt.cs diff --git a/HSMonitor/Utils/Usb/Serial/Serial.cs b/HSMonitor/Utils/Usb/Serial/Serial.cs new file mode 100644 index 0000000..aa3f2f9 --- /dev/null +++ b/HSMonitor/Utils/Usb/Serial/Serial.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.IO.Ports; +using System.Linq; +using HSMonitor.Models; +using HSMonitor.Services; + +namespace HSMonitor.Utils.Usb.Serial; + +public class Serial : IDisposable +{ + private readonly SerialPort _serialPort; + private readonly SettingsService _settingsService; + + public Serial(SettingsService settingsService) + { + _settingsService = settingsService; + if (_settingsService is {Settings: not null}) + _serialPort = new SerialPort( + string.IsNullOrEmpty(_settingsService.Settings.LastSelectedPort) + ? "COM1" + : _settingsService.Settings.LastSelectedPort, + _settingsService.Settings.LastSelectedBaudRate, + Parity.None, + 8, + StopBits.One); + else + _serialPort = new SerialPort(); + } + + public static IEnumerable GetPorts() + { + var devices = Win32DeviceMgmt + .GetAllCOMPorts(); + + devices + .Where(d => d.BusDescription?.Contains("HSMonitor") == true) + .ToList() + .ForEach(d => d.IsHsMonitorDevice = true); + + return devices; + } + + public bool CheckAccess() + { + try + { + return Open(); + } + catch + { + return false; + } + } + + private bool Open() + { + if (_serialPort.IsOpen) + return true; + + try + { + _serialPort.PortName = _settingsService.Settings.LastSelectedPort ?? "COM1"; + _serialPort.Open(); + } + catch + { + _serialPort.Close(); + throw; + } + + return _serialPort.IsOpen; + } + + public void Close() + { + if (!_serialPort.IsOpen) return; + _serialPort.Close(); + } + + public void Write(byte[] data) + { + if (!_serialPort.IsOpen) return; + try + { + _serialPort.Write(data, 0, data.Length); + } + catch + { + _serialPort.Close(); + throw; + } + } + + public void Dispose() + { + _serialPort.Close(); + _serialPort.Dispose(); + } + +} \ No newline at end of file diff --git a/HSMonitor/Utils/Usb/Win32DeviceMgmt.cs b/HSMonitor/Utils/Usb/Win32DeviceMgmt.cs new file mode 100644 index 0000000..b5f587d --- /dev/null +++ b/HSMonitor/Utils/Usb/Win32DeviceMgmt.cs @@ -0,0 +1,426 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; +using HSMonitor.Models; + +namespace HSMonitor.Utils.Usb; + +public class Win32DeviceMgmt +{ + [Flags] + public enum DiGetClassFlags : uint + { + DIGCF_DEFAULT = 0x00000001, // only valid with DIGCF_DEVICEINTERFACE + DIGCF_PRESENT = 0x00000002, + DIGCF_ALLCLASSES = 0x00000004, + DIGCF_PROFILE = 0x00000008, + DIGCF_DEVICEINTERFACE = 0x00000010, + } + + /// + /// Device registry property codes + /// + public enum SPDRP : uint + { + /// + /// DeviceDesc (R/W) + /// + SPDRP_DEVICEDESC = 0x00000000, + + /// + /// HardwareID (R/W) + /// + SPDRP_HARDWAREID = 0x00000001, + + /// + /// CompatibleIDs (R/W) + /// + SPDRP_COMPATIBLEIDS = 0x00000002, + + /// + /// unused + /// + SPDRP_UNUSED0 = 0x00000003, + + /// + /// Service (R/W) + /// + SPDRP_SERVICE = 0x00000004, + + /// + /// unused + /// + SPDRP_UNUSED1 = 0x00000005, + + /// + /// unused + /// + SPDRP_UNUSED2 = 0x00000006, + + /// + /// Class (R--tied to ClassGUID) + /// + SPDRP_CLASS = 0x00000007, + + /// + /// ClassGUID (R/W) + /// + SPDRP_CLASSGUID = 0x00000008, + + /// + /// Driver (R/W) + /// + SPDRP_DRIVER = 0x00000009, + + /// + /// ConfigFlags (R/W) + /// + SPDRP_CONFIGFLAGS = 0x0000000A, + + /// + /// Mfg (R/W) + /// + SPDRP_MFG = 0x0000000B, + + /// + /// FriendlyName (R/W) + /// + SPDRP_FRIENDLYNAME = 0x0000000C, + + /// + /// LocationInformation (R/W) + /// + SPDRP_LOCATION_INFORMATION = 0x0000000D, + + /// + /// PhysicalDeviceObjectName (R) + /// + SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E, + + /// + /// Capabilities (R) + /// + SPDRP_CAPABILITIES = 0x0000000F, + + /// + /// UiNumber (R) + /// + SPDRP_UI_NUMBER = 0x00000010, + + /// + /// UpperFilters (R/W) + /// + SPDRP_UPPERFILTERS = 0x00000011, + + /// + /// LowerFilters (R/W) + /// + SPDRP_LOWERFILTERS = 0x00000012, + + /// + /// BusTypeGUID (R) + /// + SPDRP_BUSTYPEGUID = 0x00000013, + + /// + /// LegacyBusType (R) + /// + SPDRP_LEGACYBUSTYPE = 0x00000014, + + /// + /// BusNumber (R) + /// + SPDRP_BUSNUMBER = 0x00000015, + + /// + /// Enumerator Name (R) + /// + SPDRP_ENUMERATOR_NAME = 0x00000016, + + /// + /// Security (R/W, binary form) + /// + SPDRP_SECURITY = 0x00000017, + + /// + /// Security (W, SDS form) + /// + SPDRP_SECURITY_SDS = 0x00000018, + + /// + /// Device Type (R/W) + /// + SPDRP_DEVTYPE = 0x00000019, + + /// + /// Device is exclusive-access (R/W) + /// + SPDRP_EXCLUSIVE = 0x0000001A, + + /// + /// Device Characteristics (R/W) + /// + SPDRP_CHARACTERISTICS = 0x0000001B, + + /// + /// Device Address (R) + /// + SPDRP_ADDRESS = 0x0000001C, + + /// + /// UiNumberDescFormat (R/W) + /// + SPDRP_UI_NUMBER_DESC_FORMAT = 0X0000001D, + + /// + /// Device Power Data (R) + /// + SPDRP_DEVICE_POWER_DATA = 0x0000001E, + + /// + /// Removal Policy (R) + /// + SPDRP_REMOVAL_POLICY = 0x0000001F, + + /// + /// Hardware Removal Policy (R) + /// + SPDRP_REMOVAL_POLICY_HW_DEFAULT = 0x00000020, + + /// + /// Removal Policy Override (RW) + /// + SPDRP_REMOVAL_POLICY_OVERRIDE = 0x00000021, + + /// + /// Device Install State (R) + /// + SPDRP_INSTALL_STATE = 0x00000022, + + /// + /// Device Location Paths (R) + /// + SPDRP_LOCATION_PATHS = 0x00000023, + } + + private const uint DICS_FLAG_GLOBAL = 0x00000001; + private const uint DIREG_DEV = 0x00000001; + private const uint KEY_QUERY_VALUE = 0x0001; + + /// + /// The SP_DEVINFO_DATA structure defines a device instance that is a member of a device information set. + /// + [StructLayout(LayoutKind.Sequential)] + private struct SP_DEVINFO_DATA + { + public uint cbSize; + public Guid ClassGuid; + public uint DevInst; + public UIntPtr Reserved; + } + + [StructLayout(LayoutKind.Sequential)] + struct DEVPROPKEY + { + public Guid fmtid; + public uint pid; + } + + [DllImport("setupapi.dll")] + private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); + + [DllImport("setupapi.dll", SetLastError = true)] + private static extern bool SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, uint MemberIndex, ref SP_DEVINFO_DATA DeviceInterfaceData); + + [DllImport("setupapi.dll", SetLastError = true)] + private static extern IntPtr SetupDiGetClassDevs(ref Guid gClass, uint iEnumerator, uint hParent, DiGetClassFlags nFlags); + + [DllImport("Setupapi", CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr SetupDiOpenDevRegKey(IntPtr hDeviceInfoSet, ref SP_DEVINFO_DATA deviceInfoData, uint scope, + uint hwProfile, uint parameterRegistryValueKind, uint samDesired); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegQueryValueExW", SetLastError = true)] + private static extern int RegQueryValueEx(IntPtr hKey, string lpValueName, int lpReserved, out uint lpType, + byte[] lpData, ref uint lpcbData); + + [DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)] + private static extern int RegCloseKey(IntPtr hKey); + + [DllImport("kernel32.dll")] + private static extern int GetLastError(); + + const int BUFFER_SIZE = 1024; + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiClassGuidsFromName(string ClassName, + ref Guid ClassGuidArray1stItem, uint ClassGuidArraySize, + out uint RequiredSize); + + [DllImport("setupapi.dll")] + private static extern int SetupDiClassNameFromGuid(ref Guid ClassGuid, + StringBuilder className, int ClassNameSize, ref int RequiredSize); + + /// + /// The SetupDiGetDeviceRegistryProperty function retrieves the specified device property. + /// This handle is typically returned by the SetupDiGetClassDevs or SetupDiGetClassDevsEx function. + /// + /// Handle to the device information set that contains the interface and its underlying device. + /// Pointer to an SP_DEVINFO_DATA structure that defines the device instance. + /// Device property to be retrieved. SEE MSDN + /// Pointer to a variable that receives the registry data Type. This parameter can be NULL. + /// Pointer to a buffer that receives the requested device property. + /// Size of the buffer, in bytes. + /// Pointer to a variable that receives the required buffer size, in bytes. This parameter can be NULL. + /// If the function succeeds, the return value is nonzero. + [DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)] + private static extern bool SetupDiGetDeviceRegistryProperty( + IntPtr DeviceInfoSet, + ref SP_DEVINFO_DATA DeviceInfoData, + SPDRP Property, + out uint PropertyRegDataType, + byte[] PropertyBuffer, + uint PropertyBufferSize, + out uint RequiredSize); + + [DllImport("setupapi.dll", SetLastError = true)] + static extern bool SetupDiGetDevicePropertyW( + IntPtr deviceInfoSet, + [In] ref SP_DEVINFO_DATA DeviceInfoData, + [In] ref DEVPROPKEY propertyKey, + [Out] out uint propertyType, + byte[] propertyBuffer, + uint propertyBufferSize, + out uint requiredSize, + uint flags); + + const int utf16terminatorSize_bytes = 2; + + static DEVPROPKEY DEVPKEY_Device_BusReportedDeviceDesc; + + static Win32DeviceMgmt() + { + DEVPKEY_Device_BusReportedDeviceDesc = new DEVPROPKEY(); + DEVPKEY_Device_BusReportedDeviceDesc.fmtid = new Guid(0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2); + DEVPKEY_Device_BusReportedDeviceDesc.pid = 4; + } + + public static List GetAllCOMPorts() + { + Guid[] guids = GetClassGUIDs("Ports"); + List devices = new List(); + for (int index = 0; index < guids.Length; index++) + { + IntPtr hDeviceInfoSet = SetupDiGetClassDevs(ref guids[index], 0, 0, DiGetClassFlags.DIGCF_PRESENT); + if (hDeviceInfoSet == IntPtr.Zero) + return new List(); //Failed to get device information set for the COM ports + + try + { + uint iMemberIndex = 0; + while (true) + { + try + { + SP_DEVINFO_DATA deviceInfoData = new SP_DEVINFO_DATA(); + deviceInfoData.cbSize = (uint)Marshal.SizeOf(typeof(SP_DEVINFO_DATA)); + bool success = SetupDiEnumDeviceInfo(hDeviceInfoSet, iMemberIndex, ref deviceInfoData); + if (!success) + { + // No more devices in the device information set + break; + } + + DeviceInfo deviceInfo = new DeviceInfo(); + deviceInfo.PortName = GetDeviceName(hDeviceInfoSet, deviceInfoData); + deviceInfo.Description = GetDeviceDescription(hDeviceInfoSet, deviceInfoData); + deviceInfo.BusDescription = GetDeviceBusDescription(hDeviceInfoSet, deviceInfoData); + devices.Add(deviceInfo); + + iMemberIndex++; + } + catch (Exception) + { + } + + } + } + finally + { + SetupDiDestroyDeviceInfoList(hDeviceInfoSet); + } + } + return devices; + } + + private static string GetDeviceName(IntPtr pDevInfoSet, SP_DEVINFO_DATA deviceInfoData) + { + IntPtr hDeviceRegistryKey = SetupDiOpenDevRegKey(pDevInfoSet, ref deviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE); + + if (hDeviceRegistryKey == IntPtr.Zero) + return string.Empty; //Failed to open a registry key for device-specific configuration information + + byte[] ptrBuf = new byte[BUFFER_SIZE]; + uint length = (uint)ptrBuf.Length; + try + { + uint lpRegKeyType; + int result = RegQueryValueEx(hDeviceRegistryKey, "PortName", 0, out lpRegKeyType, ptrBuf, ref length); + + if (result == 0) + return Encoding.Unicode.GetString(ptrBuf, 0, (int)length - utf16terminatorSize_bytes); + } + finally + { + RegCloseKey(hDeviceRegistryKey); + } + + return string.Empty; //Can not read registry value PortName for device + } + + private static string GetDeviceDescription(IntPtr hDeviceInfoSet, SP_DEVINFO_DATA deviceInfoData) + { + byte[] ptrBuf = new byte[BUFFER_SIZE]; + uint propRegDataType; + uint RequiredSize; + bool success = SetupDiGetDeviceRegistryProperty(hDeviceInfoSet, ref deviceInfoData, SPDRP.SPDRP_DEVICEDESC, + out propRegDataType, ptrBuf, BUFFER_SIZE, out RequiredSize); + + if (success) + return Encoding.Unicode.GetString(ptrBuf, 0, (int)RequiredSize - utf16terminatorSize_bytes); + + return string.Empty; //Can not read registry value PortName for device + } + + private static string GetDeviceBusDescription(IntPtr hDeviceInfoSet, SP_DEVINFO_DATA deviceInfoData) + { + byte[] ptrBuf = new byte[BUFFER_SIZE]; + uint propRegDataType; + uint RequiredSize; + bool success = SetupDiGetDevicePropertyW(hDeviceInfoSet, ref deviceInfoData, ref DEVPKEY_Device_BusReportedDeviceDesc, + out propRegDataType, ptrBuf, BUFFER_SIZE, out RequiredSize, 0); + + if (success) + return Encoding.Unicode.GetString(ptrBuf, 0, (int)RequiredSize - utf16terminatorSize_bytes); + + return string.Empty; //Can not read Bus provided device description device + } + + private static Guid[] GetClassGUIDs(string className) + { + uint requiredSize; + Guid[] guidArray = new Guid[1]; + + bool status = SetupDiClassGuidsFromName(className, ref guidArray[0], 1, out requiredSize); + if (status) + { + if (1 < requiredSize) + { + guidArray = new Guid[requiredSize]; + SetupDiClassGuidsFromName(className, ref guidArray[0], requiredSize, out requiredSize); + } + } + + return guidArray; + } +} \ No newline at end of file