diff --git a/HIDInterface.sln b/HIDInterface.sln index 6bc85e7..2eeb6c8 100644 --- a/HIDInterface.sln +++ b/HIDInterface.sln @@ -1,11 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -VisualStudioVersion = 12.0.30501.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2003 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USBInterface", "USBInterface\USBInterface.csproj", "{30432D4A-0128-48E7-ADCD-249D70323611}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "USBInterface", "USBInterface\USBInterface.csproj", "{30432D4A-0128-48E7-ADCD-249D70323611}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "USBInterfaceTest", "TestUSBInterface\USBInterfaceTest.csproj", "{E69C9EE3-BCA8-4E5D-8EFA-7EBD75B1087A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUSBInterface", "TestUSBInterface\TestUSBInterface.csproj", "{E69C9EE3-BCA8-4E5D-8EFA-7EBD75B1087A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,4 +25,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {FBCD496A-267D-40A5-8C67-F452E0F47DBD} + EndGlobalSection EndGlobal diff --git a/TestUSBInterface/Program.cs b/TestUSBInterface/Program.cs index 6e8ec42..e2c0ef5 100644 --- a/TestUSBInterface/Program.cs +++ b/TestUSBInterface/Program.cs @@ -1,65 +1,31 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using USBInterface; namespace TestUSBInterface { class Program { - - public static void handle(object s, USBInterface.ReportEventArgs a) + public static void Handle(object s, ReportEventArgs a) { Console.WriteLine(string.Join(", ", a.Data)); } - public static void enter(object s, EventArgs a) + public static void Enter(object s, DeviceScanner.DeviceArrivedArgs deviceArrivedArgs) { - Console.WriteLine("device arrived"); + Console.WriteLine($"Device arrived - {deviceArrivedArgs.Path}"); } - public static void exit(object s, EventArgs a) + public static void Exit(object s, DeviceScanner.DeviceRemovedArgs deviceRemovedArgs) { - Console.WriteLine("device removed"); + Console.WriteLine($"Device removed - {deviceRemovedArgs.Path}"); } static void Main(string[] args) { - // setup a scanner before hand - DeviceScanner scanner = new DeviceScanner(0x4d8, 0x3f); - scanner.DeviceArrived += enter; - scanner.DeviceRemoved += exit; + var scanner = new DeviceScanner(0x20A0, 0x4241); + scanner.DeviceArrived += Enter; + scanner.DeviceRemoved += Exit; scanner.StartAsyncScan(); - Console.WriteLine("asd"); - - // this should probably happen in enter() function - try - { - // this can all happen inside a using(...) statement - USBDevice dev = new USBDevice(0x4d8, 0x3f, null, false, 31); - - Console.WriteLine(dev.Description()); - - // add handle for data read - dev.InputReportArrivedEvent += handle; - // after adding the handle start reading - dev.StartAsyncRead(); - // can add more handles at any time - dev.InputReportArrivedEvent += handle; - - // write some data - byte[] data = new byte[32]; - data[0] = 0x00; - data[1] = 0x23; - dev.Write(data); - - dev.Dispose(); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - } + Console.WriteLine("Scanning"); Console.ReadKey(); } } diff --git a/TestUSBInterface/Properties/AssemblyInfo.cs b/TestUSBInterface/Properties/AssemblyInfo.cs deleted file mode 100644 index 2ccbebd..0000000 --- a/TestUSBInterface/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("TestUSBInterface")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("TestUSBInterface")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("e69c9ee3-bca8-4e5d-8efa-7ebd75b1087a")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/TestUSBInterface/TestUSBInterface.csproj b/TestUSBInterface/TestUSBInterface.csproj new file mode 100644 index 0000000..bd0d165 --- /dev/null +++ b/TestUSBInterface/TestUSBInterface.csproj @@ -0,0 +1,11 @@ + + + netcoreapp2.0 + Exe + x86 + $(MSBuildProgramFiles32)\dotnet\dotnet + + + + + \ No newline at end of file diff --git a/TestUSBInterface/USBInterfaceTest.csproj b/TestUSBInterface/USBInterfaceTest.csproj deleted file mode 100644 index 59ac818..0000000 --- a/TestUSBInterface/USBInterfaceTest.csproj +++ /dev/null @@ -1,64 +0,0 @@ - - - - - Debug - AnyCPU - {E69C9EE3-BCA8-4E5D-8EFA-7EBD75B1087A} - Exe - Properties - TestUSBInterface - TestUSBInterface - v4.5 - 512 - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - - - - - {30432D4A-0128-48E7-ADCD-249D70323611} - USBInterface - - - - - \ No newline at end of file diff --git a/USBInterface/ArrayHelpers.cs b/USBInterface/ArrayHelpers.cs index 66bcc5f..2ae0e4e 100644 --- a/USBInterface/ArrayHelpers.cs +++ b/USBInterface/ArrayHelpers.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace USBInterface +namespace USBInterface { public class ArrayHelpers { diff --git a/USBInterface/DeviceScanner.cs b/USBInterface/DeviceScanner.cs index c81da8a..5344f96 100644 --- a/USBInterface/DeviceScanner.cs +++ b/USBInterface/DeviceScanner.cs @@ -1,77 +1,101 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; namespace USBInterface { public class DeviceScanner - { - public event EventHandler DeviceArrived; - public event EventHandler DeviceRemoved; + { + public class DeviceRemovedArgs + { + public DeviceRemovedArgs(string path) + { + Path = path; + } + public string Path { get; } + } - public bool isDeviceConnected + public class DeviceArrivedArgs { - get { return deviceConnected; } + public DeviceArrivedArgs(string path) + { + Path = path; + } + + public string Path { get; } } - // for async reading - private object syncLock = new object(); - private Thread scannerThread; - private volatile bool asyncScanOn = false; + public event EventHandler DeviceArrived; + public event EventHandler DeviceRemoved; + + private readonly List _connectedDevices = new List(); - private volatile bool deviceConnected = false; + // for async reading + private readonly object _syncLock = new object(); + private Thread _scannerThread; + private volatile bool _asyncScanOn; - private int scanIntervalMillisecs = 10; + private int _scanIntervalMillisecs = 10; public int ScanIntervalInMillisecs { - get { lock (syncLock) { return scanIntervalMillisecs; } } - set { lock (syncLock) { scanIntervalMillisecs = value; } } + get { lock (_syncLock) { return _scanIntervalMillisecs; } } + set { lock (_syncLock) { _scanIntervalMillisecs = value; } } } - public bool isScanning - { - get { return asyncScanOn; } - } + public bool IsScanning => _asyncScanOn; - private ushort vendorId; - private ushort productId; + private readonly ushort _vendorId; + private readonly ushort _productId; // Use this class to monitor when your devices connects. // Note that scanning for device when it is open by another process will return FALSE // even though the device is connected (because the device is unavailiable) - public DeviceScanner(ushort VendorID, ushort ProductID, int scanIntervalMillisecs = 100) + public DeviceScanner(ushort vendorId, ushort productId, int scanIntervalMillisecs = 100) { - vendorId = VendorID; - productId = ProductID; + _vendorId = vendorId; + _productId = productId; ScanIntervalInMillisecs = scanIntervalMillisecs; } // scanning for device when it is open by another process will return false - public static bool ScanOnce(ushort vid, ushort pid) + public static List ScanOnce(ushort vid, ushort pid) { - return HidApi.hid_enumerate(vid, pid) != IntPtr.Zero; + var list = new List(); + + var pDev = HidApi.hid_enumerate(vid, pid); + while (pDev != IntPtr.Zero) + { + var dev = (HidDeviceInfo)Marshal.PtrToStructure(pDev, typeof(HidDeviceInfo)); + list.Add(dev); + // freeing the enumeration releases the device, + // do it as soon as you can, so we dont block device from others + HidApi.hid_free_enumeration(pDev); + pDev = dev.next; + } + return list; } public void StartAsyncScan() { // Build the thread to listen for reads - if (asyncScanOn) + if (_asyncScanOn) { // dont run more than one thread return; } - asyncScanOn = true; - scannerThread = new Thread(ScanLoop); - scannerThread.Name = "HidApiAsyncDeviceScanThread"; - scannerThread.Start(); + _asyncScanOn = true; + _scannerThread = new Thread(ScanLoop) {Name = "HidApiAsyncDeviceScanThread"}; + _scannerThread.Start(); } public void StopAsyncScan() { - asyncScanOn = false; + _asyncScanOn = false; } private void ScanLoop() @@ -82,38 +106,32 @@ private void ScanLoop() // The read has a timeout parameter, so every X milliseconds // we check if the user wants us to continue scanning. - while (asyncScanOn) + while (_asyncScanOn) { try { - IntPtr device_info = HidApi.hid_enumerate(vendorId, productId); - bool device_on_bus = device_info != IntPtr.Zero; - // freeing the enumeration releases the device, - // do it as soon as you can, so we dont block device from others - HidApi.hid_free_enumeration(device_info); - if (device_on_bus && ! deviceConnected) + var deviceInfo = ScanOnce(_vendorId, _productId); + + var newlyConnectedDevices = deviceInfo.Where(di => !_connectedDevices.Contains(di.path)).ToArray(); + var removedDevicePaths = _connectedDevices.Where(cd => deviceInfo.All(di => di.path != cd)).ToArray(); + + foreach (var newlyConnectedDevice in newlyConnectedDevices) { - // just found new device - deviceConnected = true; - if (DeviceArrived != null) - { - DeviceArrived(this, EventArgs.Empty); - } + DeviceArrived?.Invoke(this, new DeviceArrivedArgs(newlyConnectedDevice.path)); + _connectedDevices.Add(newlyConnectedDevice.path); } - if (! device_on_bus && deviceConnected) + + foreach (var removedDevicePath in removedDevicePaths) { - // just lost device connection - deviceConnected = false; - if (DeviceRemoved != null) - { - DeviceRemoved(this, EventArgs.Empty); - } + DeviceRemoved?.Invoke(this, new DeviceRemovedArgs(removedDevicePath)); + _connectedDevices.Remove(removedDevicePath); } } catch (Exception e) { // stop scan, user can manually restart again with StartAsyncScan() - asyncScanOn = false; + Console.WriteLine(e.ToString()); + _asyncScanOn = false; } // when read 0 bytes, sleep and read again Thread.Sleep(ScanIntervalInMillisecs); diff --git a/USBInterface/HidApi.cs b/USBInterface/HidApi.cs index ee9a0fc..03e068c 100644 --- a/USBInterface/HidApi.cs +++ b/USBInterface/HidApi.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; using System.Text; @@ -16,39 +14,39 @@ internal class HidApi // find the library on all platforms becasue of different // naming conventions. // Just use hidapi and expect users to supply it in same folder as .exe - public const string DLL_FILE_NAME = "hidapi"; + public const string DllFileName = "hidapi"; /// Return Type: int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_init(); /// Return Type: int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_exit(); /// Return Type: hid_device_info* ///vendor_id: unsigned short ///product_id: unsigned short - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr hid_enumerate(ushort vendor_id, ushort product_id); /// Return Type: void ///devs: struct hid_device_info* - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern void hid_free_enumeration(IntPtr devs); /// Return Type: hid_device* ///vendor_id: unsigned short ///product_id: unsigned short ///serial_number: wchar_t* - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr hid_open(ushort vendor_id, ushort product_id, [In] string serial_number); /// Return Type: hid_device* ///path: char* - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr hid_open_path([In] string path); @@ -56,7 +54,7 @@ internal class HidApi ///device: hid_device* ///data: unsigned char* ///length: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_write(IntPtr device, [In] byte[] data, uint length); @@ -65,7 +63,7 @@ internal class HidApi ///data: unsigned char* ///length: size_t->unsigned int ///milliseconds: int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_read_timeout(IntPtr device, [Out] byte[] buf_data, uint length, int milliseconds); @@ -73,22 +71,22 @@ internal class HidApi ///device: hid_device* ///data: unsigned char* ///length: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_read(IntPtr device, [Out] byte[] buf_data, uint length); /// Return Type: int ///device: hid_device* ///nonblock: int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] - public extern static int hid_set_nonblocking(IntPtr device, int nonblock); + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] + public static extern int hid_set_nonblocking(IntPtr device, int nonblock); /// Return Type: int ///device: hid_device* ///data: char* ///length: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_send_feature_report(IntPtr device, [In] byte[] data, uint length); @@ -96,21 +94,21 @@ internal class HidApi ///device: hid_device* ///data: unsigned char* ///length: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] public static extern int hid_get_feature_report(IntPtr device, [Out] byte[] buf_data, uint length); /// Return Type: void ///device: hid_device* - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl)] - public extern static void hid_close(IntPtr device); + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl)] + public static extern void hid_close(IntPtr device); /// Return Type: int ///device: hid_device* ///string: wchar_t* ///maxlen: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] public static extern int hid_get_manufacturer_string(IntPtr device, StringBuilder buf_string, uint length); @@ -118,7 +116,7 @@ internal class HidApi ///device: hid_device* ///string: wchar_t* ///maxlen: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] public static extern int hid_get_product_string(IntPtr device, StringBuilder buf_string, uint length); @@ -126,7 +124,7 @@ internal class HidApi ///device: hid_device* ///string: wchar_t* ///maxlen: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] public static extern int hid_get_serial_number_string(IntPtr device, StringBuilder buf_serial, uint maxlen); @@ -135,13 +133,13 @@ internal class HidApi ///string_index: int ///string: wchar_t* ///maxlen: size_t->unsigned int - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] public static extern int hid_get_indexed_string(IntPtr device, int string_index, StringBuilder buf_string, uint maxlen); /// Return Type: wchar_t* ///device: hid_device* - [DllImport(DLL_FILE_NAME, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] + [DllImport(DllFileName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Auto)] public static extern IntPtr hid_error(IntPtr device); diff --git a/USBInterface/Properties/AssemblyInfo.cs b/USBInterface/Properties/AssemblyInfo.cs deleted file mode 100644 index e9cedb0..0000000 --- a/USBInterface/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("USBInterface")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("USBInterface")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2014")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("6945dc49-9902-4a76-84d4-6a6e2bd81813")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/USBInterface/ReportEventArgs.cs b/USBInterface/ReportEventArgs.cs index 7fb3cad..0d98064 100644 --- a/USBInterface/ReportEventArgs.cs +++ b/USBInterface/ReportEventArgs.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace USBInterface { @@ -13,6 +10,6 @@ public ReportEventArgs(byte[] data) Data = data; } - public byte[] Data { get; private set; } + public byte[] Data { get; } } } diff --git a/USBInterface/USBDevice.cs b/USBInterface/USBDevice.cs index d56b3df..b3b144b 100644 --- a/USBInterface/USBDevice.cs +++ b/USBInterface/USBDevice.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; using System.Runtime.InteropServices; using System.Globalization; using System.Threading; @@ -16,52 +13,49 @@ public class USBDevice : IDisposable public event EventHandler InputReportArrivedEvent; public event EventHandler DeviceDisconnecedEvent; - public bool isOpen - { - get { return DeviceHandle != IntPtr.Zero; } - } + public bool IsOpen => _deviceHandle != IntPtr.Zero; // If the read process grabs ownership of device // and blocks (unable to get any data from device) // for more than Timeout millisecons // it will abandon reading, pause for readIntervalInMillisecs // and try reading again. - private int readTimeoutInMillisecs = 1; + private int _readTimeoutInMillisecs = 1; public int ReadTimeoutInMillisecs { - get { lock (syncLock) { return readTimeoutInMillisecs; } } - set { lock(syncLock) { readTimeoutInMillisecs = value; } } + get { lock (_syncLock) { return _readTimeoutInMillisecs; } } + set { lock(_syncLock) { _readTimeoutInMillisecs = value; } } } // Interval of time between two reads, // during this time the device is free and // we can write to it. - private int readIntervalInMillisecs = 4; + private int _readIntervalInMillisecs = 4; public int ReadIntervalInMillisecs { - get { lock (syncLock) { return readIntervalInMillisecs; } } - set { lock(syncLock) { readIntervalInMillisecs = value; } } + get { lock (_syncLock) { return _readIntervalInMillisecs; } } + set { lock(_syncLock) { _readIntervalInMillisecs = value; } } } // for async reading - private object syncLock = new object(); - private Thread readThread; - private volatile bool asyncReadOn = false; + private readonly object _syncLock = new object(); + private Thread _readThread; + private volatile bool _asyncReadOn; // Flag: Has Dispose already been called? // Marked as volatile because Dispose() can be called from another thread. - private volatile bool disposed = false; + private volatile bool _disposed; - private IntPtr DeviceHandle = IntPtr.Zero; + private IntPtr _deviceHandle; // this will be the return buffer for strings, // make it big, becasue by the HID spec (can not find page) // we are allowed to request more bytes than the device can return. - private StringBuilder pOutBuf = new StringBuilder(1024); + private readonly StringBuilder _pOutBuf = new StringBuilder(1024); // This is very convinient to use for the 90% of devices that // dont use ReportIDs and so have only one input report - private int DefaultInputReportLength = -1; + private readonly int _defaultInputReportLength = -1; // This only affects the read function. // receiving / sending a feature report, @@ -71,7 +65,7 @@ public int ReadIntervalInMillisecs // the prefix byte is NOT inserted. On the other hand if the device uses // Report IDs then when reading we must read +1 byte and byte 0 // of returned data array will be the Report ID. - private bool hasReportIds = false; + private readonly bool _hasReportIds; // HIDAPI does not provide any way to get or parse the HID Report Descriptor, // This means you must know in advance what it the report size for your device. @@ -79,21 +73,31 @@ public int ReadIntervalInMillisecs // // Serial Number is optional, pass null (do NOT pass an empty string) if it is unknown. // - public USBDevice(ushort VendorID - , ushort ProductID - , string serial_number - , bool HasReportIDs = true + public USBDevice(ushort vendorId + , ushort productId + , string serialNumber + , bool hasReportIDs = true + , int defaultInputReportLen = -1) + { + _deviceHandle = HidApi.hid_open(vendorId, productId, serialNumber); + AssertValidDev(); + _defaultInputReportLength = defaultInputReportLen; + _hasReportIds = hasReportIDs; + } + + public USBDevice(string path + , bool hasReportIDs = true , int defaultInputReportLen = -1) { - DeviceHandle = HidApi.hid_open(VendorID, ProductID, serial_number); + _deviceHandle = HidApi.hid_open_path(path); AssertValidDev(); - DefaultInputReportLength = defaultInputReportLen; - hasReportIds = HasReportIDs; + _defaultInputReportLength = defaultInputReportLen; + _hasReportIds = hasReportIDs; } private void AssertValidDev() { - if (DeviceHandle == IntPtr.Zero) throw new Exception("No device opened"); + if (_deviceHandle == IntPtr.Zero) throw new Exception("No device opened"); } public void GetFeatureReport(byte[] buffer, int length = -1) @@ -103,7 +107,7 @@ public void GetFeatureReport(byte[] buffer, int length = -1) { length = buffer.Length; } - if (HidApi.hid_get_feature_report(DeviceHandle, buffer, (uint)length) < 0) + if (HidApi.hid_get_feature_report(_deviceHandle, buffer, (uint)length) < 0) { throw new Exception("failed to get feature report"); } @@ -116,7 +120,7 @@ public void SendFeatureReport(byte[] buffer, int length = -1) { length = buffer.Length; } - if (HidApi.hid_send_feature_report(DeviceHandle, buffer, (uint)length) < 0) + if (HidApi.hid_send_feature_report(_deviceHandle, buffer, (uint)length) < 0) { throw new Exception("failed to send feature report"); } @@ -132,12 +136,12 @@ private int ReadRaw(byte[] buffer, int length = -1) { length = buffer.Length; } - int bytes_read = HidApi.hid_read_timeout(DeviceHandle, buffer, (uint)length, readTimeoutInMillisecs); - if (bytes_read < 0) + var bytesRead = HidApi.hid_read_timeout(_deviceHandle, buffer, (uint)length, _readTimeoutInMillisecs); + if (bytesRead < 0) { throw new Exception("Failed to Read."); } - return bytes_read; + return bytesRead; } // Meaning OutputReport @@ -148,7 +152,7 @@ private void WriteRaw(byte[] buffer, int length = -1) { length = buffer.Length; } - if (HidApi.hid_write(DeviceHandle, buffer, (uint)length) < 0) + if (HidApi.hid_write(_deviceHandle, buffer, (uint)length) < 0) { throw new Exception("Failed to write."); } @@ -157,7 +161,7 @@ private void WriteRaw(byte[] buffer, int length = -1) public string GetErrorString() { AssertValidDev(); - IntPtr ret = HidApi.hid_error(DeviceHandle); + var ret = HidApi.hid_error(_deviceHandle); // I can not find the info in the docs, but guess this frees // the ret pointer after we created a managed string object // else this would be a memory leak @@ -173,56 +177,56 @@ public string GetErrorString() // buffer beforehand and just divide the capacity by 4. public string GetIndexedString(int index) { - lock(syncLock) + lock(_syncLock) { AssertValidDev(); - if (HidApi.hid_get_indexed_string(DeviceHandle, index, pOutBuf, (uint)pOutBuf.Capacity / 4) < 0) + if (HidApi.hid_get_indexed_string(_deviceHandle, index, _pOutBuf, (uint)_pOutBuf.Capacity / 4) < 0) { throw new Exception("failed to get indexed string"); } - return pOutBuf.ToString(); + return _pOutBuf.ToString(); } } public string GetManufacturerString() { - lock (syncLock) + lock (_syncLock) { AssertValidDev(); - pOutBuf.Clear(); - if (HidApi.hid_get_manufacturer_string(DeviceHandle, pOutBuf, (uint)pOutBuf.Capacity / 4) < 0) + _pOutBuf.Clear(); + if (HidApi.hid_get_manufacturer_string(_deviceHandle, _pOutBuf, (uint)_pOutBuf.Capacity / 4) < 0) { throw new Exception("failed to get manufacturer string"); } - return pOutBuf.ToString(); + return _pOutBuf.ToString(); } } public string GetProductString() { - lock (syncLock) + lock (_syncLock) { AssertValidDev(); - pOutBuf.Clear(); - if (HidApi.hid_get_product_string(DeviceHandle, pOutBuf, (uint)pOutBuf.Capacity / 4) < 0) + _pOutBuf.Clear(); + if (HidApi.hid_get_product_string(_deviceHandle, _pOutBuf, (uint)_pOutBuf.Capacity / 4) < 0) { throw new Exception("failed to get product string"); } - return pOutBuf.ToString(); + return _pOutBuf.ToString(); } } public string GetSerialNumberString() { - lock (syncLock) + lock (_syncLock) { AssertValidDev(); - pOutBuf.Clear(); - if (HidApi.hid_get_serial_number_string(DeviceHandle, pOutBuf, (uint)pOutBuf.Capacity / 4) < 0) + _pOutBuf.Clear(); + if (HidApi.hid_get_serial_number_string(_deviceHandle, _pOutBuf, (uint)_pOutBuf.Capacity / 4) < 0) { throw new Exception("failed to get serial number string"); } - return pOutBuf.ToString(); + return _pOutBuf.ToString(); } } @@ -233,14 +237,14 @@ public string Description() , GetManufacturerString(), GetProductString(), GetSerialNumberString()); } - public void Write(byte[] user_data) + public void Write(byte[] userData) { // so we don't read and write at the same time - lock (syncLock) + lock (_syncLock) { - byte[] output_report = new byte[user_data.Length]; - Array.Copy(user_data, output_report, output_report.Length); - WriteRaw(output_report); + var outputReport = new byte[userData.Length]; + Array.Copy(userData, outputReport, outputReport.Length); + WriteRaw(outputReport); } } @@ -254,19 +258,19 @@ public void Write(byte[] user_data) // Output, Input, Feature reports. public byte[] Read(int reportLen = -1) { - lock(syncLock) + lock(_syncLock) { - int length = reportLen; + var length = reportLen; if (length < 0) { // when we have Report IDs and the user did not specify the reportLen explicitly // then add an extra byte to account for the Report ID - length = hasReportIds ? DefaultInputReportLength + 1 : DefaultInputReportLength; + length = _hasReportIds ? _defaultInputReportLength + 1 : _defaultInputReportLength; } - byte[] input_report = new byte[length]; - int read_bytes = ReadRaw(input_report); - byte[] ret = new byte[read_bytes]; - Array.Copy(input_report, 0, ret, 0, read_bytes); + var inputReport = new byte[length]; + var readBytes = ReadRaw(inputReport); + var ret = new byte[readBytes]; + Array.Copy(inputReport, 0, ret, 0, readBytes); return ret; } } @@ -274,20 +278,19 @@ public byte[] Read(int reportLen = -1) public void StartAsyncRead() { // Build the thread to listen for reads - if (asyncReadOn) + if (_asyncReadOn) { // dont run more than one read return; } - asyncReadOn = true; - readThread = new Thread(ReadLoop); - readThread.Name = "HidApiReadAsyncThread"; - readThread.Start(); + _asyncReadOn = true; + _readThread = new Thread(ReadLoop) {Name = "HidApiReadAsyncThread"}; + _readThread.Start(); } public void StopAsyncRead() { - asyncReadOn = false; + _asyncReadOn = false; } private void ReadLoop() @@ -298,25 +301,22 @@ private void ReadLoop() // The read has a timeout parameter, so every X milliseconds // we check if the user wants us to continue reading. - while (asyncReadOn) + while (_asyncReadOn) { try { - byte[] res = Read(); + var res = Read(); // when read >0 bytes, tell others about data - if (res.Length > 0 && this.InputReportArrivedEvent != null) + if (res.Length > 0) { - InputReportArrivedEvent(this, new ReportEventArgs(res)); + InputReportArrivedEvent?.Invoke(this, new ReportEventArgs(res)); } } catch (Exception) { // when read <0 bytes, means an error has occurred // stop device, break from loop and stop this thread - if (this.DeviceDisconnecedEvent != null) - { - DeviceDisconnecedEvent(this, EventArgs.Empty); - } + DeviceDisconnecedEvent?.Invoke(this, EventArgs.Empty); // call the dispose method in separate thread, // otherwise this thread would never get to die new Thread(Dispose).Start(); @@ -325,7 +325,7 @@ private void ReadLoop() // when read 0 bytes, sleep and read again // We must sleep for some time to allow others // to write to the device. - Thread.Sleep(readIntervalInMillisecs); + Thread.Sleep(_readIntervalInMillisecs); } } @@ -339,49 +339,42 @@ public void Dispose() // Protected implementation of Dispose pattern. protected virtual void Dispose(bool disposing) { - if (disposed) + if (_disposed) { return; } if (disposing) { // Free any other managed objects here. - if (asyncReadOn) + if (_asyncReadOn) { - asyncReadOn = false; - readThread.Join(readTimeoutInMillisecs); - if (readThread.IsAlive) + _asyncReadOn = false; + _readThread.Join(_readTimeoutInMillisecs); + if (_readThread.IsAlive) { - readThread.Abort(); + _readThread.Abort(); } } } // Free any UN-managed objects here. // so we are not reading or writing as the device gets closed - lock (syncLock) + lock (_syncLock) { - if (isOpen) + if (IsOpen) { - HidApi.hid_close(DeviceHandle); - DeviceHandle = IntPtr.Zero; + HidApi.hid_close(_deviceHandle); + _deviceHandle = IntPtr.Zero; } } HidApi.hid_exit(); // mark object as having been disposed - disposed = true; + _disposed = true; } ~USBDevice() { Dispose(false); } - - private string EncodeBuffer(byte[] buffer) - { - // the buffer contains trailing '\0' char to mark its end. - return Encoding.Unicode.GetString(buffer).Trim('\0'); - } - } } diff --git a/USBInterface/USBInterface.csproj b/USBInterface/USBInterface.csproj index 28ed6a0..17d3b29 100644 --- a/USBInterface/USBInterface.csproj +++ b/USBInterface/USBInterface.csproj @@ -1,59 +1,5 @@ - - - + - Debug - AnyCPU - {30432D4A-0128-48E7-ADCD-249D70323611} - Library - Properties - USBInterface - USBInterface - v4.0 - 512 - + netstandard2.0 - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - x86 - - - true - bin\Release\ - TRACE - prompt - 4 - x86 - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/USBInterface/hid_device_info.cs b/USBInterface/hid_device_info.cs new file mode 100644 index 0000000..0948065 --- /dev/null +++ b/USBInterface/hid_device_info.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace USBInterface +{ + // Used from https://stackoverflow.com/questions/29298336/using-a-c-struct-in-c-sharp + [StructLayout(LayoutKind.Sequential)] + public struct HidDeviceInfo + { + [MarshalAs(UnmanagedType.LPStr)] + public String path; + public ushort vendor_id; + public ushort product_id; + [MarshalAs(UnmanagedType.LPWStr)] + public String serial_number; + public ushort release_number; + [MarshalAs(UnmanagedType.LPWStr)] + public String manufacturer_string; + [MarshalAs(UnmanagedType.LPWStr)] + public String product_string; + public ushort usage_page; + public ushort usage; + public int interface_number; + public IntPtr next; + }; +} \ No newline at end of file