USB HID通信フロー

28897 ワード

C#USB hid通信クラスの作成
次はWIN 32 APIへの適用です.
1.Hidデバイスグローバルidの読み出し
[DllImport("hid.dll")]
  private static extern void HidD_GetHidGuid(ref Guid HidGuid);
 
2.すべてのHIDインタフェース情報セットを含むハンドルを取得する
       [DllImport("setupapi.dll", SetLastError = true)]
private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, uint Enumerator, IntPtr HwndParent, DIGCF Flags);
 
3.情報セットを巡り、各機器のインタフェース情報を得る
 
[DllImport("setupapi.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, UInt32 memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
 
4.インタフェース詳細の取得:初めての読み込みエラーですが、情報バッファの大きさを取得できます
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
 
5.インタフェース詳細の取得:2回目の読み取り可能な内容(内容はVID,PIDを含む)
SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
6.前のステップで読み取ったデバイスパス情報を利用して、CreateFileを使用してデバイスを開く
[DllImport("kernel32.dll", SetLastError = true)]
        protected static extern SafeFileHandle CreateFile(string strName, uint nAccess, uint nShareMode, uint lpSecurity, uint nCreationFlags, uint nAttributes, uint lpTemplate);
 
7.ファイルハンドルに基づいてデバイス属性情報を読み込む
[DllImport("hid.dll")]
        private static extern Boolean HidD_GetAttributes(SafeFileHandle hidDeviceObject, out HIDD_ATTRIBUTES attributes);
 
8.属性情報に基づき、VIDとPIDと下位機器が同一であると判断し、当該機器を得る
得られたマッチング装置により、準備データが得られる
[DllImport("hid.dll")]
        private static extern Boolean HidD_GetPreparsedData(SafeFileHandle hidDeviceObject, out IntPtr PreparsedData);
[DllImport("hid.dll")]
        private static extern Boolean HidD_FreePreparsedData(IntPtr PreparsedData);
 
[DllImport("hid.dll")]
        private static extern uint HidP_GetCaps(IntPtr PreparsedData, out HIDP_CAPS Capabilities);
 
9.ファイルフローの作成
hidDevice = new FileStream(device, FileAccess.ReadWrite, inputReportLength, true);
 
10.ファイルフローによるデータの読み書き
ストリームからバイトブロックを読み出し、所定のバッファにデータを書き込む.
public override int Read(byte[] array, int offset, int count);
バッファから読み出したデータを使用して、バイトブロックをストリームに書き込みます.
public override void Write(byte[] array, int offset, int count);
注意内容:
コンテンツを送信するにはoutputReportLengthの長さのコンテンツを送信する必要があります.このうち1バイト目はreportid=0 x 00で、送信内容は2バイト目から始まり、ここでは下位機に設定されたoutputReportLengthを65に取得します.
 
下位機のコンテンツを受信する場合、下位機はinputReportLengthの長さのコンテンツを送信する必要があります.ここでの長さは64です.
 
コアソース:
public class Hid : object
    {
        protected const Int32 WAIT_TIMEOUT = 0X102;
        protected const Int32 WAIT_OBJECT_0 = 0;
        protected static string strDevicePath = "";

        public UInt16 VID = 0x0483;
        public UInt16 PID = 0x5750;
        private IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
        private const int MAX_USB_DEVICES = 64;
        private bool deviceOpened = false;
        private FileStream  hidDevice = null;

        int outputReportLength;//      ,         ID
        public int OutputReportLength { get { return outputReportLength; } }
        int inputReportLength;//      ,         ID   
        public int InputReportLength { get { return inputReportLength; } }

        private Guid device_class;
        private IntPtr usb_event_handle;
        private IntPtr handle;
        private SafeFileHandle device;
        private frmMain _main;
        private Thread _readThread;
        private int _openDeviceFlag = 0;
        public int OpenDeviceFlag
        {
            get { return _openDeviceFlag; }
            set { _openDeviceFlag = value; }
        }

        public Hid(frmMain main)
        {
            device_class = HIDGuid;
            _main = main;
        }

        /// 
        /// Provides details about a single USB device
        /// 
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        protected struct DeviceInterfaceData
        {
            public int Size;
            public Guid InterfaceClassGuid;
            public int Flags;
            public IntPtr Reserved; // should correspond to ULONG_PTR but was an int
        }

        private static string GetDevicePath(IntPtr hInfoSet, ref DeviceInterfaceData oInterface)
        {
            DeviceInterfaceDetailData oDetail = new DeviceInterfaceDetailData();
            // Size workaround
            if (IntPtr.Size == 8)
                oDetail.Size = 8;
            else
                oDetail.Size = 5;
            Console.WriteLine("Size of struct: {0}", Marshal.SizeOf(oDetail)); // 4 + 256 = 260

            uint nRequiredSize = 0;

            // Error 0
            if (!SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, IntPtr.Zero, 0, ref nRequiredSize, IntPtr.Zero))
                // Error 122 - ERROR_INSUFFICIENT_BUFFER (not a problem, just used to set nRequiredSize)
                if (SetupDiGetDeviceInterfaceDetail(hInfoSet, ref oInterface, ref oDetail, nRequiredSize, ref nRequiredSize, IntPtr.Zero))
                    return oDetail.DevicePath;
               // Error 1784 - ERROR_INVALID_USER_BUFFER (unless size=5 on 32bit, size=8 on 64bit)
            return null;
        }

        /// 
        ///          
        /// 
        ///    vID
        ///    pID
        ///    serial,string serial
        /// 
        
        public HID_RETURN OpenDevice(UInt16 vID,UInt16 pID)
        {
           // IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
            if (deviceOpened == false)
            {
                //     HID  
                List deviceList = new List();
                GetHidDeviceList(ref deviceList);
                if (deviceList.Count == 0)
                    return HID_RETURN.NO_DEVICE_CONECTED;
                for (int i = 0; i < deviceList.Count; i++)
                {
                      device = CreateFile(deviceList[i], DESIREDACCESS.GENERIC_READ | DESIREDACCESS.GENERIC_WRITE, 0, 0, CREATIONDISPOSITION.OPEN_EXISTING, FLAGSANDATTRIBUTES.FILE_FLAG_OVERLAPPED, 0);
                    if (!device.IsInvalid)
                    { // strDevicePath = GetDevicePath(hInfoSet, ref oInterface);
                        HIDD_ATTRIBUTES attributes;
                        //IntPtr serialBuff = Marshal.AllocHGlobal(512);
                        HidD_GetAttributes(device, out attributes);
                        //HidD_GetSerialNumberString(device, serialBuff, 512);
                        //string deviceStr = Marshal.PtrToStringAuto(serialBuff);
                        //Marshal.FreeHGlobal(serialBuff);
                        if (attributes.VendorID == vID && attributes.ProductID == pID)   // && deviceStr == serial
                        {
                            IntPtr preparseData;
                            HIDP_CAPS caps;
                            HidD_GetPreparsedData(device, out preparseData);
                            HidP_GetCaps(preparseData, out caps);
                            HidD_FreePreparsedData(preparseData);
                            outputReportLength = caps.OutputReportByteLength;
                            inputReportLength = caps.InputReportByteLength;

                            hidDevice = new FileStream (device, FileAccess.ReadWrite, inputReportLength, true);
                            deviceOpened = true;
                            //BeginAsyncRead();
                            Guid gHid = HIDGuid;
                            IntPtr hInfoSet = SetupDiGetClassDevs(ref gHid, null, IntPtr.Zero, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
                            DeviceInterfaceData oInterface = new DeviceInterfaceData();
                            strDevicePath = GetDevicePath(hInfoSet, ref oInterface);
                            return HID_RETURN.SUCCESS;
                        }
                    }
                }
                return HID_RETURN.DEVICE_NOT_FIND;
            }
            else
                return HID_RETURN.DEVICE_OPENED;
        }

        /// 
        ///        
        /// 
        public void CloseDevice()
        {
            if (deviceOpened == true)
            {
                hidDevice.Close();
                deviceOpened = false;
            }
        }

        /// 
        ///        
        /// 
        private void BeginAsyncRead()
        {
            byte[] inputBuff = new byte[InputReportLength];
            hidDevice.BeginRead(inputBuff, 0, InputReportLength, new AsyncCallback(ReadCompleted), inputBuff);            
        }

        /// 
        ///       ,         
        /// 
        ///           
        private void ReadCompleted(IAsyncResult iResult)
        {
            byte[] readBuff = (byte[])(iResult.AsyncState);
            try
            {
                hidDevice.EndRead(iResult);//    ,              
                byte[] reportData= new byte[readBuff.Length - 1];
                for (int i = 1; i < readBuff.Length; i++)
                    reportData[i - 1] = readBuff[i];
                Report e = new Report(readBuff[0], reportData);
                //OnDataReceived(e); //        
                Define.cmdRecv.ProcData(reportData,reportData.Length);
                BeginAsyncRead();//        
            }
            catch (IOException e)//    ,       
            {
                EventArgs ex = new EventArgs();
                OnDeviceRemoved(ex);//        
                CloseDevice();

            }
        }

        /// 
        ///   :    ,            
        /// 
        public event EventHandler DataReceived;
        protected virtual void OnDataReceived(Report e)
        {
            if(DataReceived != null) DataReceived(this, e);
        }

        /// 
        ///   :    
        /// 
        public event EventHandler DeviceArrived;
        protected virtual void OnDeviceArrived( EventArgs e )
        {
            if (DeviceArrived != null) DeviceArrived(this, e);
        }

        /// 
        ///   :    
        /// 
        public event EventHandler DeviceRemoved;
        protected virtual void OnDeviceRemoved(EventArgs e)
        {
            if (DeviceRemoved != null) DeviceRemoved(this, e);
        }

        /// 
        ///   report  
        /// 
        /// 
        /// 
        public string  Write(Report r)
        {
            byte[] buffer = null;
            if (deviceOpened) 
            {
                try
                {
                     buffer = new byte[outputReportLength];
                    buffer[0] = r.reportID;
                    int maxBufferLength = 0;
                    if (r.reportBuff.Length < outputReportLength - 1)
                        maxBufferLength = r.reportBuff.Length;
                    else
                        maxBufferLength = outputReportLength - 1;
                    for (int i = 1; i < maxBufferLength; i++)
                        buffer[i] = r.reportBuff[i - 1];
                    hidDevice.Write(buffer, 0, OutputReportLength);
                    //buffer=   DataRead();

                    //Hid.ByteToHexString(buffer);
                }
                catch
                {
                    EventArgs ex = new EventArgs();
                    OnDeviceRemoved(ex);//        
                    CloseDevice();
                    throw;
                }
                
            }
            return ByteToHexString(buffer);
        }

        /// 
        ///         
        /// 
        /// 
        /// 
        public bool Write(Byte[] buf)
        {
            if (deviceOpened)
            {
                try
                {
                    hidDevice.Write(buf, 0, OutputReportLength);

                    if (_readThread == null)
                    {
                        _readThread = new Thread(new ThreadStart(DataRead));
                        _readThread.IsBackground = true;
                        _readThread.Start();
                    }
                    
                   
                    //buffer = DataRead();
                    //Hid.ByteToHexString(buffer);

                    return true;
                }
                catch
                {
                    EventArgs ex = new EventArgs();
                    OnDeviceRemoved(ex);//        
                    CloseDevice();
                    return false;
                    
                }
            }
            else
            {
                return false;
            }
        }


        private void  DataRead()
        {
            try
            {
                InputReportViaInterruptTransfer report = new InputReportViaInterruptTransfer();

                bool foundMyDevice = false;
                bool result = false;
                byte[] readBuffer = new Byte[OutputReportLength];

                report.Read(device, ref foundMyDevice, ref readBuffer, ref result);
                if (result == false)
                {
                    _main.ProgressBarEnd();
                    MessageBox.Show("    !");
                }
                _readThread = null;
            }
            catch(Exception ex)
            {
                _readThread = null;
            }
        }

        internal class InputReportViaInterruptTransfer : ReportIn
        {
            internal Boolean readyForOverlappedTransfer; //  initialize to false

            ///  
            ///  closes open handles to a device.
            ///  
            ///  
            ///   the handle for learning about the device and exchanging Feature reports. 
            ///   the handle for reading Input reports from the device. 
            ///   the handle for writing Output reports to the device. 

            internal void CancelTransfer(SafeFileHandle hidHandle, SafeFileHandle readHandle, SafeFileHandle writeHandle, IntPtr eventObject)
            {
                try
                {
                    //  ***
                    //  API function: CancelIo

                    //  Purpose: Cancels a call to ReadFile

                    //  Accepts: the device handle.

                    //  Returns: True on success, False on failure.
                    //  ***

                    CancelIo(readHandle);

                    //Console.WriteLine("************ReadFile error*************");
                    //String functionName = "CancelIo";
                    //Console.WriteLine(MyDebugging.ResultOfAPICall(functionName));
                    //Console.WriteLine("");

                    //  The failure may have been because the device was removed,
                    //  so close any open handles and
                    //  set myDeviceDetected=False to cause the application to
                    //  look for the device on the next attempt.

                    if ((!(hidHandle.IsInvalid)))
                    {
                        hidHandle.Close();
                    }

                    if ((!(readHandle.IsInvalid)))
                    {
                        readHandle.Close();
                    }

                    if ((!(writeHandle.IsInvalid)))
                    {
                        writeHandle.Close();
                    }
                }
                catch (Exception ex)
                {
                    //DisplayException(MODULE_NAME, ex);
                    throw;
                }
            }

            ///  
            ///  Creates an event object for the overlapped structure used with 
            ///  ReadFile. Called before the first call to ReadFile.
            ///  
            ///  
            ///   the overlapped structure 
            ///   the event object 

            internal void PrepareForOverlappedTransfer(ref NativeOverlapped hidOverlapped, ref IntPtr eventObject)
            {
                try
                {
                    //  ***
                    //  API function: CreateEvent

                    //  Purpose: Creates an event object for the overlapped structure used with ReadFile.

                    //  Accepts:
                    //  A security attributes structure or IntPtr.Zero.
                    //  Manual Reset = False (The system automatically resets the state to nonsignaled 
                    //  after a waiting thread has been released.)
                    //  Initial state = False (not signaled)
                    //  An event object name (optional)

                    //  Returns: a handle to the event object
                    //  ***

                    eventObject = CreateEvent(IntPtr.Zero, false, false, "");

                    //  Console.WriteLineLine(MyDebugging.ResultOfAPICall("CreateEvent"))

                    //  Set the members of the overlapped structure.

                    hidOverlapped.OffsetLow = 0;
                    hidOverlapped.OffsetHigh = 0;
                    hidOverlapped.EventHandle = eventObject;
                    readyForOverlappedTransfer = true;
                }
                catch (Exception ex)
                {
                    //DisplayException(MODULE_NAME, ex);
                    throw;
                }
            }

            ///  
            ///  reads an Input report from the device using interrupt transfers.
            ///  
            ///  
            ///   the handle for learning about the device and exchanging Feature reports. 
            ///   the handle for reading Input reports from the device. 
            ///   the handle for writing Output reports to the device. 
            ///   tells whether the device is currently attached. 
            ///   contains the requested report. 
            ///   read success 
           
           internal override void Read(SafeFileHandle readHandle, ref Boolean myDeviceDetected, ref  Byte[] inputReportBuffer, ref Boolean success)
            {
                IntPtr eventObject = CreateEvent(IntPtr.Zero, false, false, "");  //IntPtr.Zero;
                NativeOverlapped HidOverlapped = new NativeOverlapped();
                Int32 numberOfBytesRead = 0;
                Int32 result = 0;
                try
                {
                    //  If it's the first attempt to read, set up the overlapped structure for ReadFile.

                    if (readyForOverlappedTransfer == false)
                    {
                        PrepareForOverlappedTransfer(ref HidOverlapped, ref eventObject);
                    }

                    //  ***
                    //  API function: ReadFile
                    //  Purpose: Attempts to read an Input report from the device.

                    //  Accepts:
                    //  A device handle returned by CreateFile
                    //  (for overlapped I/O, CreateFile must have been called with FILE_FLAG_OVERLAPPED),
                    //  A pointer to a buffer for storing the report.
                    //  The Input report length in bytes returned by HidP_GetCaps,
                    //  A pointer to a variable that will hold the number of bytes read. 
                    //  An overlapped structure whose hEvent member is set to an event object.

                    //  Returns: the report in ReadBuffer.

                    //  The overlapped call returns immediately, even if the data hasn't been received yet.

                    //  To read multiple reports with one ReadFile, increase the size of ReadBuffer
                    //  and use NumberOfBytesRead to determine how many reports were returned.
                    //  Use a larger buffer if the application can't keep up with reading each report
                    //  individually. 
                    //  ***                    

                    success = ReadFile(readHandle, inputReportBuffer, inputReportBuffer.Length, ref numberOfBytesRead, ref HidOverlapped);

                    if (!success)
                    {
                        Console.WriteLine("waiting for ReadFile");

                        //  API function: WaitForSingleObject

                        //  Purpose: waits for at least one report or a timeout.
                        //  Used with overlapped ReadFile.

                        //  Accepts:
                        //  An event object created with CreateEvent
                        //  A timeout value in milliseconds.

                        //  Returns: A result code.

                        result = WaitForSingleObject(eventObject, 3000); //eventObject

                        //result = GetOverlappedResult(readHandle.DangerousGetHandle(), ref HidOverlapped, ref numberOfBytesRead, false);
                        //if (result != 0)
                        //    success = true;
                        //  Find out if ReadFile completed or timeout.

                        switch (result)
                        {
                            case (System.Int32)WAIT_OBJECT_0:

                                //  ReadFile has completed
                                success = true;
                                Console.WriteLine("ReadFile completed successfully.");

                                Define.cmdRecv.ProcData(inputReportBuffer, inputReportBuffer.Length);

                                break;
                            case WAIT_TIMEOUT:

                                //  Cancel the operation on timeout
                                //  CancelTransfer(hidHandle, readHandle, writeHandle, eventObject);
                                Console.WriteLine("Readfile timeout");
                                success = false;
                                myDeviceDetected = false;
                                
                                break;
                            default:

                                //  Cancel the operation on other error.
                                //CancelTransfer(hidHandle, readHandle, writeHandle, eventObject);
                                Console.WriteLine("Readfile undefined error");
                                success = false;
                                myDeviceDetected = false;
                                break;
                        }

                    }
                     
                }
                catch (Exception ex)
                {
                    ////DisplayException(MODULE_NAME, ex);
                    throw;
                }
            }
        }
        internal abstract class ReportIn
        {
            ///  
            ///  Each class that handles reading reports defines a Read method for reading 
            ///  a type of report. Read is declared as a Sub rather
            ///  than as a Function because asynchronous reads use a callback method 
            ///  that can access parameters passed by ByRef but not Function return values.
            ///  

            internal abstract void Read(SafeFileHandle readHandle,  ref Boolean myDeviceDetected, ref Byte[] readBuffer, ref Boolean success);
        }


        /// 
        ///        hid     
        /// 
        ///               
        public static void GetHidDeviceList(ref List deviceList)
        {
            Guid hUSB = Guid.Empty;
            uint index = 0;

            deviceList.Clear();
            //   hid    id
            HidD_GetHidGuid(ref hUSB);
            //        HID         
            IntPtr hidInfoSet = SetupDiGetClassDevs(ref hUSB, 0, IntPtr.Zero, DIGCF.DIGCF_PRESENT | DIGCF.DIGCF_DEVICEINTERFACE);
            if (hidInfoSet != IntPtr.Zero)
            {
                SP_DEVICE_INTERFACE_DATA interfaceInfo = new SP_DEVICE_INTERFACE_DATA();
                interfaceInfo.cbSize = Marshal.SizeOf(interfaceInfo);
                //          
                for (index = 0; index < MAX_USB_DEVICES; index++)
                {
                    //   index     
                    if (SetupDiEnumDeviceInterfaces(hidInfoSet, IntPtr.Zero, ref hUSB, index, ref interfaceInfo))
                    {
                        int buffsize = 0;
                        //         :       ,             
                        SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, IntPtr.Zero, buffsize, ref buffsize, null);
                        //      
                        IntPtr pDetail = Marshal.AllocHGlobal(buffsize);
                        SP_DEVICE_INTERFACE_DETAIL_DATA detail = new SP_DEVICE_INTERFACE_DETAIL_DATA();
                        detail.cbSize = Marshal.SizeOf(typeof(SP_DEVICE_INTERFACE_DETAIL_DATA));
                        Marshal.StructureToPtr(detail, pDetail, false);
                        if (SetupDiGetDeviceInterfaceDetail(hidInfoSet, ref interfaceInfo, pDetail, buffsize, ref buffsize, null))
                        {
                            deviceList.Add(Marshal.PtrToStringAuto((IntPtr)((int)pDetail + 4)));
                        }
                        Marshal.FreeHGlobal(pDetail);
                    }
                }
            }
            SetupDiDestroyDeviceInfoList(hidInfoSet);
            //return deviceList.ToArray();
        }

        public void ParseMessages( ref Message m )
        {
            if (m.Msg == DEVICE_FLAG.WM_DEVICECHANGE)    // we got a device change message! A USB device was inserted or removed
            {
                switch (m.WParam.ToInt32())    // Check the W parameter to see if a device was inserted or removed
                {
                    case DEVICE_FLAG.DEVICE_ARRIVAL:    // inserted
                        Console.WriteLine("ParseMessages     ");
                        if (DeviceArrived != null)
                        {
                            DeviceArrived(this, new EventArgs());
                        }
                        CheckDevicePresent();
                        break;
                    case DEVICE_FLAG.DEVICE_REMOVECOMPLETE:    // removed
                        Console.WriteLine("ParseMessages     ");
                        if (DeviceRemoved != null)
                        {
                            DeviceRemoved(this, new EventArgs());
                        }
                        CheckDevicePresent();
                        break;
                }
            }
        }

        public void RegisterHandle( IntPtr Handle )
        {
            usb_event_handle = RegisterForUsbEvents(Handle, device_class);
            this.handle = Handle;
            //Check if the device is already present.
            CheckDevicePresent();
        }

        public bool CheckDevicePresent()
        {
            HID_RETURN hid_ret;
            string sSerial="";
            try
            {
                hid_ret = OpenDevice(VID, PID);
                if (hid_ret == HID_RETURN.SUCCESS)
                {
                    Console.WriteLine("      ");
                    _openDeviceFlag = (int)HID_RETURN.SUCCESS;
                    return true;
                }
                else
                {
                    _openDeviceFlag = (int)hid_ret;
                    return false;
                }
            }
            catch (System.Exception ex)
            {

                return false;
            }
        }

        /// 
        /// Helper to get the HID guid.
        /// 
        public static Guid HIDGuid
        {
            get
            {
                Guid gHid = Guid.Empty;
                HidD_GetHidGuid(ref gHid);
                return gHid;
                //return new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED"); //gHid;
            }
        }