/********************************************************************
FileName: USBLib.cs
Date: 11/8/2009
Author: Helmut Obertanner (flash [at] x4u [dot] de)
Company: X4U electronix [http://www.x4u.de]
Software License Agreement:
The software supplied herewith by X4U
(the “Author”) is free for personal use only. Distribution of sources
is not allowed withut permisson of the Author. The software is owned
by the Author, and is protected under applicable copyright laws.
All rights are reserved.
Any use in violation of the foregoing restrictions may subject the
user to criminal sanctions under applicable laws, as well as to
civil liability for the breach of the terms and conditions of this
license.
THIS SOFTWARE IS PROVIDED IN AN “AS IS” CONDITION. NO WARRANTIES,
WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED
TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
PARTICULAR PURPOSE APPLY TO THIS SOFTWARE. THE AUTHOR SHALL NOT,
IN ANY CIRCUMSTANCES, BE LIABLE FOR SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES, FOR ANY REASON WHATSOEVER.
********************************************************************/
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
namespace X4U.BlackBox {
///<summary>
/// The BoxState Class is used to parse the result returned from
/// USB device and provides a human readable form of the data
///</summary>
internal class BoxState {
public int State { get; private set; }
public int Volume { get; private set; }
public int VolumePercent { get; private set; }
public int Duration { get; private set; }
public int DurationPercent { get; private set; }
public int Frequency { get; private set; }
public int FrequencyPercent { get; private set; }
public bool Inputdetected { get; private set; }
public int Mode { get; private set; }
public bool Led1Enabled { get; private set; }
public bool Led2Enabled { get; private set; }
public bool Led3Enabled { get; private set; }
///<summary>
/// .ctor Parses the buffer returned fomr USB and calculate the values for the
/// controls.
///</summary>
///<param name="buffer"></param>
public BoxState(byte[] buffer) {
switch (buffer[0]) {
case USBLib.BOX_GET_STATE:
State = (int)buffer[1];
Volume = (int)(buffer[3] << 8) + buffer[2];
VolumePercent = (int)(LogToLin(Volume, 1023d) / 10.23d); //(Max AD Value is 1023)
Duration = (int)(buffer[5] << 8) + buffer[4];
DurationPercent = (int)(LogToLin(Duration, 1023d) / 10.23d);
Frequency = (int)(buffer[7] << 8) + buffer[6];
FrequencyPercent = (int)(LogToLin(Frequency, 1023d) / 10.23d);
Inputdetected = (buffer[8] == 1);
Mode = buffer[9];
Led1Enabled = (buffer[10] == 1);
Led2Enabled = (buffer[11] == 1);
Led3Enabled = (buffer[12] == 1);
//... and so on...
break;
}
}
///<summary>
/// Required Conversion from Logarithmic Potentiometers to Linear scale
///</summary>
///<param name="value">The current value</param>
///<param name="maxValue">The maximum value</param>
///<returns>Returns the corrected value for the controls (percent)</returns>
private static double LogToLin(double value, double maxValue){
return (Math.Sqrt(value) / Math.Sqrt(maxValue)) * maxValue;
}
}
///<summary>
/// Class is used to communicate with the USB-HID Device
///</summary>
///<example>
/// // define a class wide variable:
/// USBLib _usb = new USBLib("Vid_04d8&Pid_XXXX&rev_XXXX");
///
///
/// // In a Timer.Tick event or a Backgroundthread do the following:
/// private void timer1_Tick(object sender, EventArgs e) {
/// if (_usb.IsDeviceConnected()) {
/// SetConnectedState(true); // Led
/// BoxState currentState = _usb.GetState(); // Reads the Data from USB
/// SetVolume(currentState.VolumePercent); // Knob
/// SetDuration(currentState.DurationPercent); // Knob
/// SetFrequency(currentState.FrequencyPercent); // Knob
/// SetImpulseState(currentState.Inputdetected); // Led
/// SetSwitchState(currentState.Mode == 1); // Led
/// SetLed1State(currentState.Led1Enabled); // Led
/// SetLed2State(currentState.Led2Enabled); // Led
/// SetLed3State(currentState.Led3Enabled); // Led
/// } else {
/// SetConnectedState(false); // Led
/// }
/// }
///
/// // Issues a direct command
/// private void button1_Click(object sender, EventArgs e) {
/// if (_usb.IsDeviceConnected()) {
/// _usb.ClearMemory();
/// }
/// }
///</example>
internal class USBLib {
///<summary>
/// .ctor takes the device ID as parameter
///</summary>
///<param name="deviceId">Your deviceID: "Vid_04d8&Pid_XXXX&rev_XXXX"</param>
public USBLib(string deviceId) {
_deviceId = deviceId.ToLowerInvariant();
}
///<summary>
/// The Control Commands for the device
///</summary>
public const byte BOX_GET_STATE = 0x80;
public const byte BOX_GET_MEMORY_STATE = 0x81;
public const byte BOX_CLEAR_MEMORY = 0x82;
public const byte BOX_GET_VOLUME = 0x83;
public const byte BOX_SET_VOLUME = 0x84;
public const byte BOX_GET_OUT1_STATE = 0x85;
public const byte BOX_GET_OUT2_STATE = 0x86;
public const byte BOX_GET_OUT3_STATE = 0x87;
public const byte BOX_SET_OUT1_STATE = 0x88;
public const byte BOX_SET_OUT2_STATE = 0x89;
public const byte BOX_SET_OUT3_STATE = 0x90;
public const byte BOX_GET_LED1_STATE = 0x91;
public const byte BOX_GET_LED2_STATE = 0x92;
public const byte BOX_GET_LED3_STATE = 0x93;
public const byte BOX_SET_LED1_STATE = 0x94;
public const byte BOX_SET_LED2_STATE = 0x95;
public const byte BOX_SET_LED3_STATE = 0x96;
#region API Stuff
const int DIGCF_PRESENT = 0x00000002;
const int DIGCF_DEVICEINTERFACE = 0x00000010;
const int DIGCF_INTERFACEDEVICE = 0x00000010;
const uint GENERIC_READ = 0x80000000;
const uint GENERIC_WRITE = 0x40000000;
const uint FILE_SHARE_READ = 0x00000001;
const uint FILE_SHARE_WRITE = 0x00000002;
const uint OPEN_EXISTING = 3;
const int ERROR_NO_MORE_ITEMS = 259;
enum SPDRP {
SPDRP_DEVICEDESC = 0x00000000,
SPDRP_HARDWAREID = 0x00000001,
SPDRP_COMPATIBLEIDS = 0x00000002,
SPDRP_NTDEVICEPATHS = 0x00000003,
SPDRP_SERVICE = 0x00000004,
SPDRP_CONFIGURATION = 0x00000005,
SPDRP_CONFIGURATIONVECTOR = 0x00000006,
SPDRP_CLASS = 0x00000007,
SPDRP_CLASSGUID = 0x00000008,
SPDRP_DRIVER = 0x00000009,
SPDRP_CONFIGFLAGS = 0x0000000A,
SPDRP_MFG = 0x0000000B,
SPDRP_FRIENDLYNAME = 0x0000000C,
SPDRP_LOCATION_INFORMATION = 0x0000000D,
SPDRP_PHYSICAL_DEVICE_OBJECT_NAME = 0x0000000E,
SPDRP_CAPABILITIES = 0x0000000F,
SPDRP_UI_NUMBER = 0x00000010,
SPDRP_UPPERFILTERS = 0x00000011,
SPDRP_LOWERFILTERS = 0x00000012,
SPDRP_MAXIMUM_PROPERTY = 0x00000013,
}
[StructLayout(LayoutKind.Sequential)]
struct SP_DEVINFO_DATA {
public Int32 cbSize;
public Guid ClassGuid;
public uint DevInst;
public IntPtr Reserved;
}
[StructLayout(LayoutKind.Sequential)]
struct SP_DEVICE_INTERFACE_DATA {
public Int32 cbSize;
public Guid interfaceClassGuid;
public Int32 flags;
private UIntPtr reserved;
}
// Device interface detail data
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct SP_DEVICE_INTERFACE_DETAIL_DATA {
public Int32 cbSize;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string DevicePath;
}
//Returns a HDEVINFO type for a device information set (USB HID devices in
//our case). We will need the HDEVINFO as in input parameter for calling many of
//the other SetupDixxx() functions.
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiGetClassDevs", SetLastError = true)]
static extern IntPtr SetupDiGetClassDevs(
ref Guid ClassGuid, //Input: Supply the class GUID here.
[MarshalAs(UnmanagedType.LPTStr)] string Enumerator, //Input: Use NULL here, not important for our purposes
IntPtr hwndParent, //Input: Use NULL here, not important for our purposes
int Flags); //Input: Flags describing what kind of filtering to use.
//Gives us "PSP_DEVICE_INTERFACE_DATA" which contains the Interface specific GUID (different
//from class GUID). We need the interface GUID to get the device path.
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiEnumDeviceInterfaces", SetLastError = true)]
static extern bool SetupDiEnumDeviceInterfaces(
IntPtr DeviceInfoSet, //Input: Give it the HDEVINFO we got from SetupDiGetClassDevs()
IntPtr DeviceInfoData, //Input (optional)
ref Guid InterfaceClassGuid, //Input
int MemberIndex, //Input: "Index" of the device you are interested in getting the path for.
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); //Output: This function fills in an "SP_DEVICE_INTERFACE_DATA" structure.
//SetupDiDestroyDeviceInfoList() frees up memory by destroying a DeviceInfoList
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiDestroyDeviceInfoList", SetLastError = true)]
static extern bool SetupDiDestroyDeviceInfoList(
IntPtr DeviceInfoSet); //Input: Give it a handle to a device info list to deallocate from RAM.
//SetupDiEnumDeviceInfo() fills in an "SP_DEVINFO_DATA" structure, which we need for SetupDiGetDeviceRegistryProperty()
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiEnumDeviceInfo", SetLastError = true)]
static extern bool SetupDiEnumDeviceInfo(
IntPtr DeviceInfoSet,
int MemberIndex,
ref SP_DEVINFO_DATA DeviceInfoData);
//SetupDiGetDeviceRegistryProperty() gives us the hardware ID, which we use to check to see if it has matching VID/PID
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiGetDeviceRegistryProperty", SetLastError = true)]
static extern bool SetupDiGetDeviceRegistryProperty(
IntPtr DeviceInfoSet,
ref SP_DEVINFO_DATA DeviceInfoData,
int Property,
out int PropertyRegDataType,
IntPtr PropertyBuffer,
int PropertyBufferSize,
out int RequiredSize);
//SetupDiGetDeviceInterfaceDetail() gives us a device path, which is needed before CreateFile() can be used.
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiGetDeviceInterfaceDetail", SetLastError = true)]
static extern bool SetupDiGetDeviceInterfaceDetail(
IntPtr DeviceInfoSet, //Input: Wants HDEVINFO which can be obtained from SetupDiGetClassDevs()
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, //Input: Pointer to an structure which defines the device interface.
ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, //Output: Pointer to a strucutre, which will contain the device path.
int DeviceInterfaceDetailDataSize, //Input: Number of bytes to retrieve.
out int RequiredSize, //Output (optional): The number of bytes needed to hold the entire struct
int DeviceInfoData); //Output
//SetupDiGetDeviceInterfaceDetail() gives us a device path, which is needed before CreateFile() can be used.
[DllImport("setupapi.dll", CharSet = CharSet.Ansi, EntryPoint = "SetupDiGetDeviceInterfaceDetail", SetLastError = true)]
static extern unsafe bool SetupDiGetDeviceInterfaceDetail(
IntPtr DeviceInfoSet, //Input: Wants HDEVINFO which can be obtained from SetupDiGetClassDevs()
ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, //Input: Pointer to an structure which defines the device interface.
int DeviceInterfaceDetailData, //Output: Pointer to a strucutre, which will contain the device path.
int DeviceInterfaceDetailDataSize, //Input: Number of bytes to retrieve.
out int RequiredSize, //Output (optional): The number of bytes needed to hold the entire struct
int DeviceInfoData); //Output
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
static extern SafeFileHandle CreateFile(
string lpFileName,
uint dwDesiredAccess,
uint dwShareMode,
int lpSecurityAttributes,
uint dwCreationDisposition,
uint dwFlagsAndAttributes,
int hTemplateFile);
#endregion
object _lockObject = new Object();
///<summary>
/// The Device-ID in form of "Vid_04d8&Pid_XXXX&rev_XXXX"
///</summary>
string _deviceId;
///<summary>
/// The idea is to check for the device, and when it's present remember the devicepath
/// So I don't need to leave a handle open the whole time.
/// Instead I use the CreateFile Method for each transmission.
///</summary>
string _usbDevicePath;
///<summary>
/// The Interface GUID
///</summary>
Guid _interfaceClassGuid = new Guid(0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
///<summary>
/// Checks if the Device is connected and gets the Device Path.
///</summary>
///<returns>True, when the device is connected to the PC</returns>
public bool IsDeviceConnected() {
bool isConnected = false;
int errorStatus = 0;
int interfaceIndex = 0;
string enummerator = null;
// First populate a list of plugged in devices (by specifying "DIGCF_PRESENT"), which are of the specified class GUID.
IntPtr deviceInfoTable = SetupDiGetClassDevs(ref _interfaceClassGuid, enummerator, IntPtr.Zero, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
// Now look through the list we just populated. We are trying to see if any of them match our device.
while (true) {
SP_DEVICE_INTERFACE_DATA interfaceData = new SP_DEVICE_INTERFACE_DATA();
interfaceData.cbSize = Marshal.SizeOf(interfaceData);
if (SetupDiEnumDeviceInterfaces(deviceInfoTable, IntPtr.Zero, ref _interfaceClassGuid, interfaceIndex, ref interfaceData)) {
errorStatus = Marshal.GetLastWin32Error();
if (errorStatus == ERROR_NO_MORE_ITEMS) //Did we reach the end of the list of matching devices in the DeviceInfoTable?
{ //Cound not find the device. Must not have been attached.
break;
}
} else { //Else some other kind of unknown error ocurred...
errorStatus = Marshal.GetLastWin32Error();
break;
}
// No Error
//Now retrieve the hardware ID from the registry. The hardware ID contains the VID and PID, which we will then
//check to see if it is the correct device or not.
//Initialize an appropriate SP_DEVINFO_DATA structure. We need this structure for SetupDiGetDeviceRegistryProperty().
SP_DEVINFO_DATA devInfo = new SP_DEVINFO_DATA();
devInfo.cbSize = Marshal.SizeOf(devInfo);
SetupDiEnumDeviceInfo(deviceInfoTable, interfaceIndex, ref devInfo);
IntPtr propertyBuffer = IntPtr.Zero;
int propertyRegDataType;
int requiredSize;
//First query for the size of the hardware ID, so we can know how big a buffer to allocate for the data.
SetupDiGetDeviceRegistryProperty(deviceInfoTable, ref devInfo, (int)SPDRP.SPDRP_HARDWAREID, out propertyRegDataType, propertyBuffer, 0, out requiredSize);
//Allocate a buffer for the hardware ID.
propertyBuffer = Marshal.AllocHGlobal((int)requiredSize + 4);
if (propertyBuffer == IntPtr.Zero) { //if null, error, couldn't allocate enough memory
//Can't really recover from this situation, just exit instead.
break;
}
int dummySize = 0;
//Retrieve the hardware IDs for the current device we are looking at. PropertyValueBuffer gets filled with a
//REG_MULTI_SZ (array of null terminated strings). To find a device, we only care about the very first string in the
//buffer, which will be the "device ID". The device ID is a string which contains the VID and PID, in the example
//format "Vid_04d8&Pid_003f".
SetupDiGetDeviceRegistryProperty(deviceInfoTable, ref devInfo, (int)SPDRP.SPDRP_HARDWAREID, out propertyRegDataType, propertyBuffer, requiredSize, out dummySize);
//Now check if the first string in the hardware ID matches the device ID of my USB device.
String deviceIDFromRegistry = Marshal.PtrToStringAnsi(propertyBuffer).ToLowerInvariant();
Marshal.FreeHGlobal(propertyBuffer);
if (deviceIDFromRegistry.Contains(_deviceId)) {
//Device must have been found. Open read and write handles. In order to do this, we will need the actual device path first.
//We can get the path by calling SetupDiGetDeviceInterfaceDetail(), however, we have to call this function twice: The first
//time to get the size of the required structure/buffer to hold the detailed interface data, then a second time to actually
//get the structure (after we have allocated enough memory for the structure.)
requiredSize = 0;
dummySize = 0;
//First call populates "StructureSize" with the correct value
SetupDiGetDeviceInterfaceDetail(deviceInfoTable, ref interfaceData, 0, 0, out requiredSize, 0);
int size = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
IntPtr ptr = Marshal.AllocHGlobal(requiredSize);
SP_DEVICE_INTERFACE_DETAIL_DATA detailData = (SP_DEVICE_INTERFACE_DETAIL_DATA)Marshal.PtrToStructure(ptr, typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
detailData.cbSize = requiredSize;
//Now call SetupDiGetDeviceInterfaceDetail() a second time to receive the goods.
SetupDiGetDeviceInterfaceDetail(deviceInfoTable, ref interfaceData, ref detailData, requiredSize, out requiredSize, 0);
if (!string.IsNullOrEmpty(detailData.DevicePath)) {
string path = "\\\\?\\" + detailData.DevicePath;
if (path != _usbDevicePath) {
SafeFileHandle handle = CreateFile(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (!handle.IsInvalid) {
_usbDevicePath = path;
isConnected = true;
} else {
_usbDevicePath = string.Empty;
}
handle.Close();
} else {
isConnected = true;
}
}
Marshal.FreeHGlobal(ptr);
break;
}
interfaceIndex++;
}
// Cleanup
SetupDiDestroyDeviceInfoList(deviceInfoTable);
return isConnected;
}
//public void ToggleLeds() {
// byte[] buffer = new byte[65];
// buffer[0] = 0;
// buffer[1] = 0x80;
// SafeFileHandle hWrite = CreateFile(_usbDevicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
// FileStream writer = new FileStream(hWrite, FileAccess.Write, 65);
// writer.Write (buffer,0,buffer.Length);
// writer.Close();
//}
public void ClearMemory() {
WriteCommand(BOX_CLEAR_MEMORY);
}
public void SetOutput(byte output, bool value) {
WriteCommand((byte)(BOX_SET_OUT1_STATE + output), (byte)(value ? 1 : 0));
}
public BoxState GetState() {
return new BoxState(ReadData(BOX_GET_STATE));
}
///<summary>
/// Sends just a command byte to the device.
///</summary>
///<param name="command">The command to send</param>
private void WriteCommand(byte command) {
WriteCommand(command, 0);
}
///<summary>
/// Sends a command byte to the device and a value
///</summary>
///<param name="command">The command to send</param>
///<param name="value">The value parameter for the command</param>
private void WriteCommand(byte command, byte value) {
lock (_lockObject) {
// buffer is always data len +1
byte[] buffer = new byte[65];
buffer[0] = 0; // the first byte is parsed by the usb stack
buffer[1] = command;
buffer[2] = value;
try {
SafeFileHandle hWrite = CreateFile(_usbDevicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (!hWrite.IsInvalid) {
FileStream writer = new FileStream(hWrite, FileAccess.Write, buffer.Length);
writer.Write(buffer, 0, buffer.Length);
writer.Close();
} else {
hWrite.Close();
}
} catch (System.Exception ex) {
//TODO: need some Exception handling
}
}
}
///<summary>
/// Sends a command to the device and reads the values returnd from device
///</summary>
///<param name="command">The command to send</param>
///<returns>64 bytes of ddevice data</returns>
private byte[] ReadData(byte command) {
lock (_lockObject) {
// buffer for receiving the data
byte[] buffer = new byte[65];
buffer[0] = 0;
buffer[1] = command;
try {
SafeFileHandle hWrite = CreateFile(_usbDevicePath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (hWrite.IsInvalid) {
hWrite.Close();
} else {
SafeFileHandle hRead = CreateFile(_usbDevicePath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
FileStream writer = new FileStream(hWrite, FileAccess.Write, buffer.Length);
writer.Write(buffer, 0, buffer.Length);
FileStream reader = new FileStream(hRead, FileAccess.Read);
reader.ReadByte(); // remove the control byte
for (int index = 0; index < buffer.Length - 1; index++) {
buffer[index] = (byte)reader.ReadByte();
}
reader.Close();
writer.Close();
}
} catch (System.Exception ex) {
//TODO: need some Exception handling
}
return buffer;
}
}
}
}