-
Notifications
You must be signed in to change notification settings - Fork 38
/
HIDInterface.cs
516 lines (431 loc) · 21.5 KB
/
HIDInterface.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using Microsoft.Win32.SafeHandles;
using System.IO;
namespace HIDInterface
{
internal class HIDDevice
{
#region constants
private const int DIGCF_DEFAULT = 0x1;
private const int DIGCF_PRESENT = 0x2;
private const int DIGCF_ALLCLASSES = 0x4;
private const int DIGCF_PROFILE = 0x8;
private const int DIGCF_DEVICEINTERFACE = 0x10;
private const short FILE_ATTRIBUTE_NORMAL = 0x80;
private const short INVALID_HANDLE_VALUE = -1;
private const uint GENERIC_READ = 0x80000000;
private const uint GENERIC_WRITE = 0x40000000;
private const uint FILE_SHARE_READ = 0x00000001;
private const uint FILE_SHARE_WRITE = 0x00000002;
private const uint CREATE_NEW = 1;
private const uint CREATE_ALWAYS = 2;
private const uint OPEN_EXISTING = 3;
#endregion
#region win32_API_declarations
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid,
IntPtr Enumerator,
IntPtr hwndParent,
uint Flags);
[DllImport("hid.dll", SetLastError = true)]
private static extern void HidD_GetHidGuid(ref Guid hidGuid);
[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiEnumDeviceInterfaces(
IntPtr hDevInfo,
//ref SP_DEVINFO_DATA devInfo,
IntPtr devInfo,
ref Guid interfaceClassGuid,
UInt32 memberIndex,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData
);
[DllImport(@"setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiGetDeviceInterfaceDetail(
IntPtr hDevInfo,
ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData,
ref SP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData,
UInt32 deviceInterfaceDetailDataSize,
out UInt32 requiredSize,
ref SP_DEVINFO_DATA deviceInfoData
);
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern SafeFileHandle CreateFile(string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes, uint dwCreationDisposition,
uint dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadFile(SafeFileHandle hFile, byte[] lpBuffer,
uint nNumberOfBytesToRead, ref uint lpNumberOfBytesRead, IntPtr lpOverlapped);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteFile(SafeFileHandle hFile, byte[] lpBuffer,
uint nNumberOfBytesToWrite, ref uint lpNumberOfBytesWritten, IntPtr lpOverlapped);
[DllImport("hid.dll", SetLastError = true)]
private static extern bool HidD_GetPreparsedData(
SafeFileHandle hObject,
ref IntPtr PreparsedData);
[DllImport("hid.dll", SetLastError = true)]
private static extern Boolean HidD_FreePreparsedData(ref IntPtr PreparsedData);
[DllImport("hid.dll", SetLastError = true)]
private static extern int HidP_GetCaps(
IntPtr pPHIDP_PREPARSED_DATA, // IN PHIDP_PREPARSED_DATA PreparsedData,
ref HIDP_CAPS myPHIDP_CAPS); // OUT PHIDP_CAPS Capabilities
[DllImport("hid.dll", SetLastError = true)]
private static extern Boolean HidD_GetAttributes(SafeFileHandle hObject, ref HIDD_ATTRIBUTES Attributes);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_GetFeature(
IntPtr hDevice,
IntPtr hReportBuffer,
uint ReportBufferLength);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_SetFeature(
IntPtr hDevice,
IntPtr ReportBuffer,
uint ReportBufferLength);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_GetProductString(
SafeFileHandle hDevice,
IntPtr Buffer,
uint BufferLength);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern bool HidD_GetSerialNumberString(
SafeFileHandle hDevice,
IntPtr Buffer,
uint BufferLength);
[DllImport("hid.dll", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
private static extern Boolean HidD_GetManufacturerString(
SafeFileHandle hDevice,
IntPtr Buffer,
uint BufferLength);
#endregion
#region structs
public struct interfaceDetails
{
public string manufacturer;
public string product;
public int serialNumber;
public ushort VID;
public ushort PID;
public string devicePath;
public int IN_reportByteLength;
public int OUT_reportByteLength;
public ushort versionNumber;
}
// HIDP_CAPS
[StructLayout(LayoutKind.Sequential)]
private struct HIDP_CAPS
{
public System.UInt16 Usage; // USHORT
public System.UInt16 UsagePage; // USHORT
public System.UInt16 InputReportByteLength;
public System.UInt16 OutputReportByteLength;
public System.UInt16 FeatureReportByteLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
public System.UInt16[] Reserved; // USHORT Reserved[17];
public System.UInt16 NumberLinkCollectionNodes;
public System.UInt16 NumberInputButtonCaps;
public System.UInt16 NumberInputValueCaps;
public System.UInt16 NumberInputDataIndices;
public System.UInt16 NumberOutputButtonCaps;
public System.UInt16 NumberOutputValueCaps;
public System.UInt16 NumberOutputDataIndices;
public System.UInt16 NumberFeatureButtonCaps;
public System.UInt16 NumberFeatureValueCaps;
public System.UInt16 NumberFeatureDataIndices;
}
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVINFO_DATA
{
public uint cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential)]
private struct SP_DEVICE_INTERFACE_DATA
{
public uint cbSize;
public Guid InterfaceClassGuid;
public uint Flags;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct SP_DEVICE_INTERFACE_DETAIL_DATA
{
public int cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DevicePath;
}
[StructLayout(LayoutKind.Sequential)]
private struct HIDD_ATTRIBUTES
{
public Int32 Size;
public Int16 VendorID;
public Int16 ProductID;
public Int16 VersionNumber;
}
[StructLayout(LayoutKind.Sequential)]
private struct COMMTIMEOUTS
{
public UInt32 ReadIntervalTimeout;
public UInt32 ReadTotalTimeoutMultiplier;
public UInt32 ReadTotalTimeoutConstant;
public UInt32 WriteTotalTimeoutMultiplier;
public UInt32 WriteTotalTimeoutConstant;
}
#endregion
#region globals
public bool deviceConnected { get; set; }
private SafeFileHandle handle_read;
private SafeFileHandle handle_write;
private FileStream FS_read;
private FileStream FS_write;
private HIDP_CAPS capabilities;
public interfaceDetails productInfo;
public event dataReceivedEvent dataReceived; //The calling class can subscribe to this event
public delegate void dataReceivedEvent(byte[] message);
public byte[] readData;
private bool useAsyncReads;
#endregion
#region static_methods
public static interfaceDetails[] getConnectedDevices()
{
interfaceDetails[] devices = new interfaceDetails[0];
//Create structs to hold interface information
SP_DEVINFO_DATA devInfo = new SP_DEVINFO_DATA();
SP_DEVICE_INTERFACE_DATA devIface = new SP_DEVICE_INTERFACE_DATA();
devInfo.cbSize = (uint)Marshal.SizeOf(devInfo);
devIface.cbSize = (uint)(Marshal.SizeOf(devIface));
Guid G = new Guid();
HidD_GetHidGuid(ref G); //Get the guid of the HID device class
IntPtr i = SetupDiGetClassDevs(ref G, IntPtr.Zero, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
//Loop through all available entries in the device list, until false
SP_DEVICE_INTERFACE_DETAIL_DATA didd = new SP_DEVICE_INTERFACE_DETAIL_DATA();
if (IntPtr.Size == 8) // for 64 bit operating systems
didd.cbSize = 8;
else
didd.cbSize = 4 + Marshal.SystemDefaultCharSize; // for 32 bit systems
int j = -1;
bool b = true;
int error;
SafeFileHandle tempHandle;
while (b)
{
j++;
b = SetupDiEnumDeviceInterfaces(i, IntPtr.Zero, ref G, (uint)j, ref devIface);
error = Marshal.GetLastWin32Error();
if (b == false)
break;
uint requiredSize = 0;
bool b1 = SetupDiGetDeviceInterfaceDetail(i, ref devIface, ref didd, 256, out requiredSize, ref devInfo);
string devicePath = didd.DevicePath;
//create file handles using CT_CreateFile
tempHandle = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
//get capabilites - use getPreParsedData, and getCaps
//store the reportlengths
IntPtr ptrToPreParsedData = new IntPtr();
bool ppdSucsess = HidD_GetPreparsedData(tempHandle, ref ptrToPreParsedData);
if (ppdSucsess == false)
continue;
HIDP_CAPS capabilities = new HIDP_CAPS();
int hidCapsSucsess = HidP_GetCaps(ptrToPreParsedData, ref capabilities);
HIDD_ATTRIBUTES attributes = new HIDD_ATTRIBUTES();
bool hidAttribSucsess = HidD_GetAttributes(tempHandle, ref attributes);
string productName = "";
string SN = "";
string manfString = "";
IntPtr buffer = Marshal.AllocHGlobal(126);//max alloc for string;
if (HidD_GetProductString(tempHandle, buffer, 126)) productName = Marshal.PtrToStringAuto(buffer);
if (HidD_GetSerialNumberString(tempHandle, buffer, 126)) SN = Marshal.PtrToStringAuto(buffer);
if (HidD_GetManufacturerString(tempHandle, buffer, 126)) manfString = Marshal.PtrToStringAuto(buffer);
Marshal.FreeHGlobal(buffer);
//Call freePreParsedData to release some stuff
HidD_FreePreparsedData(ref ptrToPreParsedData);
//If connection was sucsessful, record the values in a global struct
interfaceDetails productInfo = new interfaceDetails();
productInfo.devicePath = devicePath;
productInfo.manufacturer = manfString;
productInfo.product = productName;
productInfo.PID = (ushort)attributes.ProductID;
productInfo.VID = (ushort)attributes.VendorID;
productInfo.versionNumber = (ushort)attributes.VersionNumber;
productInfo.IN_reportByteLength = (int)capabilities.InputReportByteLength;
productInfo.OUT_reportByteLength = (int)capabilities.OutputReportByteLength;
if (stringIsInteger(SN))
productInfo.serialNumber = Convert.ToInt32(SN); //Check that serial number is actually a number
int newSize = devices.Length + 1;
Array.Resize(ref devices, newSize);
devices[newSize - 1] = productInfo;
}
SetupDiDestroyDeviceInfoList(i);
return devices;
}
#endregion
#region constructors
/// <summary>
/// Creates an object to handle read/write functionality for a USB HID device
/// Uses one filestream for each of read/write to allow for a write to occur during a blocking
/// asnychronous read
/// </summary>
/// <param name="VID">The vendor ID of the USB device to connect to</param>
/// <param name="PID">The product ID of the USB device to connect to</param>
/// <param name="serialNumber">The serial number of the USB device to connect to</param>
/// <param name="useAsyncReads">True - Read the device and generate events on data being available</param>
public HIDDevice(ushort VID, ushort PID, ushort serialNumber, bool useAsyncReads)
{
interfaceDetails[] devices = getConnectedDevices();
//loop through all connected devices to find one with the correct details
for (int i = 0; i < devices.Length; i++)
{
if ((devices[i].VID == VID) && (devices[i].PID == PID) && (devices[i].serialNumber == (int)serialNumber))
initDevice(devices[i].devicePath, useAsyncReads);
}
if (!deviceConnected)
{
string hexVID = numToHexString(VID);
string hexPID = numToHexString(PID);
throw new Exception("Device with VID: 0x" + hexVID + " PID: 0x" + hexPID + " SerialNumber: " + serialNumber.ToString() + " could not be found");
}
}
/// <summary>
/// Creates an object to handle read/write functionality for a USB HID device
/// Uses one filestream for each of read/write to allow for a write to occur during a blocking
/// asnychronous read
/// </summary>
/// <param name="devicePath">The USB device path - from getConnectedDevices</param>
/// <param name="useAsyncReads">True - Read the device and generate events on data being available</param>
public HIDDevice(string devicePath, bool useAsyncReads)
{
initDevice(devicePath, useAsyncReads);
if (!deviceConnected)
{
throw new Exception("Device could not be found");
}
}
#endregion
#region functions
private void initDevice(string devicePath, bool useAsyncReads)
{
deviceConnected = false;
//create file handles using CT_CreateFile
handle_read = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
handle_write = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
//get capabilites - use getPreParsedData, and getCaps
//store the reportlengths
IntPtr ptrToPreParsedData = new IntPtr();
bool ppdSucsess = HidD_GetPreparsedData(handle_read, ref ptrToPreParsedData);
capabilities = new HIDP_CAPS();
int hidCapsSucsess = HidP_GetCaps(ptrToPreParsedData, ref capabilities);
HIDD_ATTRIBUTES attributes = new HIDD_ATTRIBUTES();
bool hidAttribSucsess = HidD_GetAttributes(handle_read, ref attributes);
string productName = "";
string SN = "";
string manfString = "";
IntPtr buffer = Marshal.AllocHGlobal(126);//max alloc for string;
if (HidD_GetProductString(handle_read, buffer, 126)) productName = Marshal.PtrToStringAuto(buffer);
if (HidD_GetSerialNumberString(handle_read, buffer, 126)) SN = Marshal.PtrToStringAuto(buffer);
if (HidD_GetManufacturerString(handle_read, buffer, 126)) manfString = Marshal.PtrToStringAuto(buffer);
Marshal.FreeHGlobal(buffer);
//Call freePreParsedData to release some stuff
HidD_FreePreparsedData(ref ptrToPreParsedData);
//SetupDiDestroyDeviceInfoList(i);
if (handle_read.IsInvalid)
return;
deviceConnected = true;
//If connection was sucsessful, record the values in a global struct
productInfo = new interfaceDetails();
productInfo.devicePath = devicePath;
productInfo.manufacturer = manfString;
productInfo.product = productName;
productInfo.serialNumber = Convert.ToInt32(SN);
productInfo.PID = (ushort)attributes.ProductID;
productInfo.VID = (ushort)attributes.VendorID;
productInfo.versionNumber = (ushort)attributes.VersionNumber;
productInfo.IN_reportByteLength = (int)capabilities.InputReportByteLength;
productInfo.OUT_reportByteLength = (int)capabilities.OutputReportByteLength;
//use a filestream object to bring this stuff into .NET
FS_read = new FileStream(handle_read, FileAccess.ReadWrite, capabilities.OutputReportByteLength, false);
FS_write = new FileStream(handle_write, FileAccess.ReadWrite, capabilities.InputReportByteLength, false);
this.useAsyncReads = useAsyncReads;
if (useAsyncReads)
readAsync();
}
public void close()
{
if (FS_read != null)
FS_read.Close();
if (FS_write != null)
FS_write.Close();
if ((handle_read != null) && (!(handle_read.IsInvalid)))
handle_read.Close();
if ((handle_write != null) && (!(handle_write.IsInvalid)))
handle_write.Close();
this.deviceConnected = false;
}
public void write(byte[] data)
{
if (data.Length > capabilities.OutputReportByteLength)
throw new Exception("Output report must not exceed " + (capabilities.OutputReportByteLength - 1).ToString() + " bytes");
//uint numBytesWritten = 0;
byte[] packet = new byte[capabilities.OutputReportByteLength];
Array.Copy(data, 0, packet, 1, data.Length); //start at 1, as the first byte must be zero for HID report
packet[0] = 0;
if (FS_write.CanWrite)
FS_write.Write(packet, 0, packet.Length);
else
throw new Exception("Filestream unable to write");
}
//This read function will be used with asychronous operation, called by the constructor if async reads are used
private void readAsync()
{
readData = new byte[capabilities.InputReportByteLength];
if (FS_read.CanRead)
FS_read.BeginRead(readData, 0, readData.Length, new AsyncCallback(GetInputReportData), readData);
else
throw new Exception("Device is unable to read");
}
private void GetInputReportData(IAsyncResult ar)
{
FS_read.EndRead(ar); //must call an endread before starting another one
//TODO handle exception with PCB is reaet
//Reset the read thread to read the next report
if (FS_read.CanRead)
FS_read.BeginRead(readData, 0, readData.Length, new AsyncCallback(GetInputReportData), readData);
else
throw new Exception("Device is unable to read");
dataReceived(readData); //triggers the event to be heard by the calling class
}
/// <summary>
/// This read function is for normal synchronous reads
/// </summary>
/// <returns></returns>
public byte[] read()
{
if (useAsyncReads == true)
throw new Exception("A synchonous read cannot be executed when operating in async mode");
//Call readFile
byte[] readBuf = new byte[capabilities.InputReportByteLength];
FS_read.Read(readBuf, 0, readBuf.Length);
return readBuf;
}
#endregion
#region utilities
public static bool stringIsInteger(string val)
{
Double result;
return Double.TryParse(val, System.Globalization.NumberStyles.Integer,
System.Globalization.CultureInfo.CurrentCulture, out result);
}
public static string numToHexString(ushort num)
{
return String.Format("{0:X}", num);
}
#endregion
}
}