This is the latest working repository used in our demo video for the Maxim to display temperature readings on Bluetooth

Dependencies:   USBDevice

hspguisourcev301/HspGuiSourceV301/HSPGui/HID.cs

Committer:
darienf
Date:
2021-05-02
Revision:
5:bc128a16232f
Parent:
3:36de8b9e4b1a

File content as of revision 5:bc128a16232f:

using System;
using System.Collections;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32.SafeHandles;

//------------------------------------------------------------------------------------------
// OS24EVK-59 split into HeartRateApp EXE and MAX30101 DLL.
// Moved all MAX30101 DLL classes into namespace Maxim.MAX30101GUI
// Moved all HeartRateApp GUI classes into namespace Maxim.MAX30101
// OS24EVK-59 Create separate project that builds Maxim.MAX30101GUI DLL library

// OS24EVK-59 moved class HID into namespace Maxim.MAX30101 instead of namespace HeartRateApp
namespace Maxim.MAX30101
{
#pragma warning disable 1574
    /// <summary>
    /// USB Human Interface Device functions to connect to EV kit without requiring a custom device driver
    /// </summary>
#pragma warning restore 1574
    public class HID
    {
        private IntPtr EventObject;
        private System.Threading.NativeOverlapped managedOverlapped;
        private IntPtr nonManagedOverlapped;
        private IntPtr nonManagedBuffer;
        public SafeFileHandle writeHandle;
        public ArrayList writeHandleArray = new ArrayList();
        public SafeFileHandle readHandle;
        public ArrayList readHandleArray = new ArrayList();
        public ArrayList desiredHIDPathNameArray = new ArrayList();
        public String deviceID = System.String.Format("Vid_{0:x4}&Pid_{1:x4}", support.DEFAULT_VENDOR_ID, support.DEFAULT_PRODUCT_ID);

        private bool explicit_report_id = true;
        private const byte DEFAULT_REPORT_ID = 0;
        private const byte SHORT_REPORT_ID = 1;
        public const byte HID_REPORT_ID_1 = 1; // MAX30101 optical data, 3 bytes per channel, up to 3 channels per sample
        public const byte HID_REPORT_ID_2 = 2; // LIS2DH accelerometer data, 2 bytes per channel, 3 channels per sample
        public const byte HID_REPORT_ID_3 = 3; // reserved
        private const byte USB_OVERFLOW = 8;
        private const byte USB_WRITE_ERROR = 4;
        private const byte USB_READ_ERROR = 2;
        public const byte I2C_NACK_ERROR = 1;
        private const byte API_FAIL = 0;     //API functions return non-0 upon success, to easily denote 'true' for C programs
        private const byte I2C_SUCCESS = 3;  //we'll also use a non-0 value to denote success, since returning a '0' would lead to trouble when mixed with API's returns.
        private const byte GP_SUCCESS = 3;
        private const byte GP_FAIL = 0;
        private const byte REPORT_SIZE = 64;

        // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] before FlushQueue()
        // mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
        // mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        public static Mutex mutexGuardIOBuf = new Mutex();
        // TODO1: OS24EVK-57 2015-04-01 myHID.readHID2() needs to return result into IOBuf. But IOBuf = new byte[64], do we replace with IOBuf = new byte[128]? Does this affect readHID()?
        public byte[] IOBuf = new byte[REPORT_SIZE];

        private const int IOtimeout = 10000;

        // API declarations relating to device management (SetupDixxx and 
        // RegisterDeviceNotification functions).   

        // from dbt.h

        internal const Int32 DBT_DEVNODES_CHANGED = 7; 
        internal const Int32 DBT_DEVICEARRIVAL = 0X8000;
        internal const Int32 DBT_DEVICEREMOVECOMPLETE = 0X8004;
        internal const Int32 DBT_DEVTYP_DEVICEINTERFACE = 5;
        internal const Int32 DBT_DEVTYP_HANDLE = 6;
        internal const Int32 DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4;
        internal const Int32 DEVICE_NOTIFY_SERVICE_HANDLE = 1;
        internal const Int32 DEVICE_NOTIFY_WINDOW_HANDLE = 0;
        internal const Int32 WM_DEVICECHANGE = 0X219;
        internal const Int32 SPDRP_HARDWAREID = 1;

        // from setupapi.h

        internal const Int32 DIGCF_PRESENT = 2;
        internal const Int32 DIGCF_DEVICEINTERFACE = 0X10;

        // Two declarations for the DEV_BROADCAST_DEVICEINTERFACE structure.

        // Use this one in the call to RegisterDeviceNotification() and
        // in checking dbch_devicetype in a DEV_BROADCAST_HDR structure:

        [StructLayout(LayoutKind.Sequential)]
        internal class DEV_BROADCAST_DEVICEINTERFACE
        {
            internal Int32 dbcc_size;
            internal Int32 dbcc_devicetype;
            internal Int32 dbcc_reserved;
            internal Guid dbcc_classguid;
            internal Int16 dbcc_name;
        }

        // Use this to read the dbcc_name String and classguid:

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal class DEV_BROADCAST_DEVICEINTERFACE_1
        {
            internal Int32 dbcc_size;
            internal Int32 dbcc_devicetype;
            internal Int32 dbcc_reserved;
            [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.U1, SizeConst = 16)]
            internal Byte[] dbcc_classguid;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 255)]
            internal Char[] dbcc_name;
        }

        [StructLayout(LayoutKind.Sequential)]
        internal class DEV_BROADCAST_HDR
        {
            internal Int32 dbch_size;
            internal Int32 dbch_devicetype;
            internal Int32 dbch_reserved;
        }

        internal struct SP_DEVICE_INTERFACE_DATA        
        {
            internal Int32 cbSize;
            internal System.Guid InterfaceClassGuid;
            internal Int32 Flags;
            internal IntPtr Reserved;
        }

        //[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        //internal struct SP_DEVICE_INTERFACE_DETAIL_DATA
        //{
        //    internal Int32 cbSize;
        //    //internal String DevicePath;            
        //    internal Char[] DevicePath;
        //}

// warning CS0649: Field 'Maxim.MAX30101.HID.SP_DEVINFO_DATA.cbSize' is never assigned to, and will always have its default value 0
#pragma warning disable 0649
        internal struct SP_DEVINFO_DATA
        {
            internal Int32 cbSize;
            internal System.Guid ClassGuid;
            internal Int32 DevInst;
            internal Int32 Reserved;
        }
#pragma warning restore 0649

        //HDEVINFO SetupDiGetClassDevs(const GUID *ClassGuid, PCTSTR Enumerator, HWND hwndParent, DWORD Flags);
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        internal static extern IntPtr SetupDiGetClassDevs(ref System.Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, Int32 Flags);

        //BOOL SetupDiDestroyDeviceInfoList(HDEVINFO DeviceInfoSet);
        [DllImport("setupapi.dll", SetLastError = true)]
        internal static extern Boolean SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);

        //BOOL SetupDiEnumDeviceInterfaces(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, const GUID *InterfaceClassGuid, DWORD MemberIndex, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
        [DllImport("setupapi.dll", SetLastError = true)]
        internal static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref System.Guid InterfaceClassGuid, Int32 MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);
        [DllImport("setupapi.dll", SetLastError = true)]
        // required to pass DeviceInfoData=null
        internal static extern Boolean SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref System.Guid InterfaceClassGuid, Int32 MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);

        //BOOL SetupDiGetDeviceInterfaceDetail(HDEVINFO DeviceInfoSet, PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData, PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, DWORD DeviceInterfaceDetailDataSize, PDWORD RequiredSize, PSP_DEVINFO_DATA DeviceInfoData);
        //[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        //internal  extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData);
        //[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        //// required to pass DeviceInfoData=null
        //internal  extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, ref SP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, IntPtr DeviceInfoData);
        // cannot get SP_DEVICE_INTERFACE_DETAIL_DATA's DevicePath field to work properly, so use IntPtr instead of ref SP_DEVICE_INTERFACE_DATA
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        // required to pass DeviceInterfaceDetailData=null
        internal static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData); 
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        // required to pass DeviceInterfaceDetailData=null, DeviceInfoData=null
        internal static extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, IntPtr DeviceInfoData); 

        //BOOL SetupDiEnumDeviceInfo(HDEVINFO DeviceInfoSet, DWORD MemberIndex, PSP_DEVINFO_DATA DeviceInfoData);
        [DllImport("setupapi.dll", SetLastError = true)]
        internal static extern Boolean SetupDiEnumDeviceInfo(IntPtr DeviceInfoSet, Int32 MemberIndex, ref SP_DEVINFO_DATA DevInfoData);

        //BOOL SetupDiGetDeviceRegistryProperty(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData, DWORD Property, PDWORD PropertyRegDataType, PBYTE PropertyBuffer, DWORD PropertyBufferSize, PDWORD RequiredSize);
        [DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
        internal static extern Boolean SetupDiGetDeviceRegistryProperty(IntPtr DeviceInfoSet, ref SP_DEVINFO_DATA DevInfoData, Int32 Property, IntPtr PropertyRegDataType, IntPtr PropertyBuffer, Int32 PropertyBufferSize, ref Int32 RequiredSize);
        
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        internal static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, Int32 Flags);

        [DllImport("user32.dll", SetLastError = true)]
        internal static extern Boolean UnregisterDeviceNotification(IntPtr Handle);

        //  API declarations for HID communications.

        //  from hidpi.h
        //  Typedef enum defines a set of integer constants for HidP_Report_Type

        internal const Int16 HidP_Input = 0;
        internal const Int16 HidP_Output = 1;
        internal const Int16 HidP_Feature = 2;

        [StructLayout(LayoutKind.Sequential)]
        internal struct HIDD_ATTRIBUTES
        {
            internal Int32 Size;
            internal UInt16 VendorID;
            internal UInt16 ProductID;
            internal UInt16 VersionNumber;
        }

        [DllImport("hid.dll", SetLastError = true)]
        internal static extern Boolean HidD_FlushQueue(SafeFileHandle HidDeviceObject);

        [DllImport("hid.dll", SetLastError = true)]
        internal static extern Boolean HidD_GetAttributes(SafeFileHandle HidDeviceObject, ref HIDD_ATTRIBUTES Attributes);

        [DllImport("hid.dll", SetLastError = true)]
        internal static extern void HidD_GetHidGuid(ref System.Guid HidGuid);

        public void getHidGuid(ref System.Guid hidGuid)
        {
            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
#if DEBUG
            HidD_GetHidGuid(ref hidGuid);
#else
            try
            {
                HidD_GetHidGuid(ref hidGuid);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
#endif
            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        public string DebugMessage;
        //public void TraceMessage(string message,
        //[System.Runtime.CompilerServices.CallerMemberName] string memberName = "",
        //[System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",
        //[System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)
        //{
        //    Console.WriteLine("message: " + message);
        //    Console.WriteLine("member name: " + memberName);
        //    Console.WriteLine("source file path: " + sourceFilePath);
        //    Console.WriteLine("source line number: " + sourceLineNumber);
        //}

        /// <summary>
        /// FindDesiredHIDPathNamesFromGuid() only appends to desiredHIDPathNameArray. 
        /// Desired HIDs that have been removed from the system are removed from desiredHIDPathNameArray in openHIDhandles
        /// </summary>
        /// <param name="myGuid"></param>
        private void FindDesiredHIDPathNamesFromGuid(System.Guid myGuid)
        {
            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
            // But I never see this kind of exception thrown from within the C# GUI.
            // HID.findHIDs() still raises the exception in Matlab, 
            // Message: Arithmetic operation resulted in an overflow.
            //
            //    % Required: connect to MAX30101EVKIT hardware
            //    fprintf('Connecting to MAX30101EVKIT hardware...\n');
            //    for trial=0:2000
            //        try
            //            %pause(1) % delay at least 1 second
            //            myMAX30101.myHID.findHIDs();
            //            % Sometimes we get Error using MAX30101Example
            //            %   If this happens, try clearing the workspace and run again.
            //            % Message: Arithmetic operation resulted in an overflow.
            //            % FindDesiredHIDPathNamesFromGuid
            //            % findHIDs
            //            % Source: MAX30101
            //            % HelpLink:    
            //            if (myMAX30101.myHID.isConnected())
            //                break
            //            end
            //        catch me
            //            % disp(me)
            //        end
            //    end
            //
            // If matlab does successfully connect to USB, it is able to get 
            // streaming data through the PartialArrayIntAvailable event 
            // handler -- even though it can't understand 
            // System.Collections.ArrayList data, it does at least understand 
            // Array<System.Int32> or int[] data.
            //
            Int32 memberIndex = 0;
            Int32 bufferSize = 0;
            IntPtr deviceInfoSet = new System.IntPtr();
            SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
            IntPtr deviceInterfaceDetailDataBuffer = IntPtr.Zero;

            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
            // diagnostic: trying to avoid "Arithmetic overflow" from matlab. Limit the number of HID devices to be checked.
            const int memberIndexLimit = 100;
            
#if DEBUG
#else
            try
            {
#endif
            DebugMessage = string.Format("{0} first deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);", System.Reflection.MethodInfo.GetCurrentMethod().Name);
            deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

                memberIndex = 0;

                // The cbSize element of the DeviceInterfaceData structure must be set to the structure's size in bytes. 
                // The size is 28 bytes for 32-bit code and 32 bits for 64-bit code.
                DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);

                while (memberIndex < memberIndexLimit)
                {
                    // Begin with memberIndex = 0 and increment through the device information set until no more devices are available.
                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiEnumDeviceInterfaces ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref myGuid, memberIndex, ref DeviceInterfaceData))
                    {
                        break;
                    }

                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, IntPtr.Zero);
                    //tempLastError = GetLastError();
                    //if (tempLastError != ERROR_INSUFFICIENT_BUFFER)  // ERROR_INSUFFICIENT_BUFFER is expected on this first call
                    //    break;
                    //FIXME add error check

                    // Allocate memory for the SP_DEVICE_INTERFACE_DETAIL_DATA structure using the returned buffer size.
                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.AllocHGlobal(bufferSize) ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    deviceInterfaceDetailDataBuffer = Marshal.AllocHGlobal(bufferSize);
                    // Returns a System.IntPtr pointer to the newly allocated global heap memory.
                    // This memory must be released using the Marshal.FreeHGlobal method.
                    // Marshal.AllocHGlobal(numBytes) could throw OutOfMemoryException ?

                    // Store cbSize in the first bytes of the array. The number of bytes varies with 32- and 64-bit systems.
                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.WriteInt32 ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    Marshal.WriteInt32(deviceInterfaceDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);

                    // Call SetupDiGetDeviceInterfaceDetail again.
                    // This time, pass a pointer to DetailDataBuffer and the returned required buffer size.
                    DebugMessage = string.Format("{0} memberIndex={1} second SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, deviceInterfaceDetailDataBuffer, bufferSize, ref bufferSize, IntPtr.Zero))
                        break;

                    // Skip over cbsize (4 bytes) to get the address of the devicePathName.
                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
                    DebugMessage = string.Format("{0} memberIndex={1} IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4);
                    // DebugMessage = string.Format("{0} memberIndex={1} new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    // BAD CODE. IntPtr pDevicePathName = new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4);
                    // BAD CODE. assumes the pointer is a 32-bit address, intermittently fails from 64-bit matlab client.

                    // Get the String containing the devicePathName.
                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.PtrToStringAuto(pDevicePathName); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    string tempPathName = Marshal.PtrToStringAuto(pDevicePathName);

                    // match any device pathname that contains deviceID (case-insensitive match) "Vid_{0:x4}&Pid_{1:x4}"
                    DebugMessage = string.Format("{0} memberIndex={1} if (tempPathName.ToLower().IndexOf(deviceID.ToLower()) != -1) ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    if (tempPathName.ToLower().IndexOf(deviceID.ToLower()) != -1)
                    {
                        DebugMessage = string.Format("{0} memberIndex={1} desiredHIDPathNameArray.Add(tempPathName); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                        desiredHIDPathNameArray.Add(tempPathName);
                    }

                    DebugMessage = string.Format("{0} memberIndex={1} memberIndex = memberIndex + 1; ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);

                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
                    // why didn't they call Marshal.FreeHGlobal here, inside the while loop? Isn't this a memory leak?
                    // Free the memory allocated previously by AllocHGlobal.
                    if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
                    {
                        Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
                        deviceInterfaceDetailDataBuffer = IntPtr.Zero;
                    }
                    
                    memberIndex = memberIndex + 1;
                }
#if DEBUG
                // Free the memory allocated previously by AllocHGlobal.
                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);

                if (deviceInfoSet != IntPtr.Zero)
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
#else
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
            finally
            {
                // Free the memory allocated previously by AllocHGlobal.
                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);

                if (deviceInfoSet != IntPtr.Zero)
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
            }
#endif
                DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        private void FindAllHIDPathNamesFromGuid(System.Guid myGuid, ArrayList allHIDPathNameArray)
        {
            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
            // But I never see this kind of exception thrown from within the C# GUI.
            // HID.findHIDs() still raises the exception in Matlab, 
            // Message: Arithmetic operation resulted in an overflow.
            Int32 memberIndex = 0; 
            Int32 bufferSize = 0;
            IntPtr deviceInfoSet = new System.IntPtr();
            SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA(); 
            IntPtr deviceInterfaceDetailDataBuffer = IntPtr.Zero;

            // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
            // diagnostic: trying to avoid "Arithmetic overflow" from matlab. Limit the number of HID devices to be checked.
            const int memberIndexLimit = 100;
            
            
#if DEBUG
#else
            try
            {
#endif
            DebugMessage = string.Format("{0} first deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);", System.Reflection.MethodInfo.GetCurrentMethod().Name);
            deviceInfoSet = SetupDiGetClassDevs(ref myGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

                memberIndex = 0;

                // The cbSize element of the DeviceInterfaceData structure must be set to the structure's size in bytes. 
                // The size is 28 bytes for 32-bit code and 32 bits for 64-bit code.
                DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);

                // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
                while (memberIndex < memberIndexLimit)
                {
                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiEnumDeviceInterfaces ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    if (!SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref myGuid, memberIndex, ref DeviceInterfaceData))
                    {
                        break;
                    }

                    DebugMessage = string.Format("{0} memberIndex={1} first SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, IntPtr.Zero, 0, ref bufferSize, IntPtr.Zero);

                    // Allocate memory for the SP_DEVICE_INTERFACE_DETAIL_DATA structure using the returned buffer size.
                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.AllocHGlobal(bufferSize) ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    deviceInterfaceDetailDataBuffer = Marshal.AllocHGlobal(bufferSize);

                    // Store cbSize in the first bytes of the array. The number of bytes varies with 32- and 64-bit systems.
                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.WriteInt32 ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    Marshal.WriteInt32(deviceInterfaceDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);

                    // Call SetupDiGetDeviceInterfaceDetail again.
                    // This time, pass a pointer to deviceInterfaceDetailDataBuffer and the returned required buffer size.
                    DebugMessage = string.Format("{0} memberIndex={1} second SetupDiGetDeviceInterfaceDetail ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    if (!SetupDiGetDeviceInterfaceDetail(deviceInfoSet, ref DeviceInterfaceData, deviceInterfaceDetailDataBuffer, bufferSize, ref bufferSize, IntPtr.Zero))
                        break;

                    // Skip over cbsize (4 bytes) to get the address of the devicePathName.
                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
                    DebugMessage = string.Format("{0} memberIndex={1} IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    // example: IntPtr right way to add an offset (portable to 64-bit clients)
                    IntPtr pDevicePathName = IntPtr.Add(deviceInterfaceDetailDataBuffer, 4);
                    // DebugMessage = string.Format("{0} memberIndex={1} IntPtr pDevicePathName = new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    // BAD CODE. IntPtr pDevicePathName = new IntPtr(deviceInterfaceDetailDataBuffer.ToInt32() + 4);
                    // BAD CODE. assumes the pointer is a 32-bit address, intermittently fails from 64-bit matlab client.

                    // Get the String containing the devicePathName.
                    DebugMessage = string.Format("{0} memberIndex={1} Marshal.PtrToStringAuto(pDevicePathName); ", System.Reflection.MethodInfo.GetCurrentMethod().Name, memberIndex);
                    allHIDPathNameArray.Add(Marshal.PtrToStringAuto(pDevicePathName));

                    // VERIFY: https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message
                    // why didn't they call Marshal.FreeHGlobal here, inside the while loop? Isn't this a memory leak?
                    // Free the memory allocated previously by AllocHGlobal.
                    if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
                    {
                        Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);
                        deviceInterfaceDetailDataBuffer = IntPtr.Zero;
                    }
                   
                    memberIndex = memberIndex + 1;
                }
#if DEBUG
                // Free the memory allocated previously by AllocHGlobal.
                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);

                if (deviceInfoSet != IntPtr.Zero)
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
#else
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
            finally
            {
                // Free the memory allocated previously by AllocHGlobal.
                if (deviceInterfaceDetailDataBuffer != IntPtr.Zero)                    
                    Marshal.FreeHGlobal(deviceInterfaceDetailDataBuffer);                

                if (deviceInfoSet != IntPtr.Zero)
                    SetupDiDestroyDeviceInfoList(deviceInfoSet);
            }
#endif
            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        public bool isConnected()
        {
            try
            {
                return desiredHIDPathNameArray.Count != 0 ? true : false;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }


        /// <summary>
        /// Called when a WM_DEVICECHANGE message has arrived,
        /// indicating that a device has been attached or removed.
        /// 
        /// </summary>
        /// <param name="m"> a message with information about the device </param>
        /// <returns>true on HID device arrival or remove complete</returns>
        public bool HandleWMDeviceChangeMessage(Message m)
        {
            // Example code:
            // 
            // <code>
            // protected override void WndProc(ref Message m)
            // {
            //     if (myHID != null)
            //     {
            //         if (myHID.HandleWMDeviceChangeMessage(m) /* m.Msg == HID.WM_DEVICECHANGE */ )
            //         {
            //             // optional: handle newly arrived connection or surprise disconnect
            //         }
            //     }
            //     // Let the base form process the message.
            //     base.WndProc(ref m);
            // }
            // </code>
            // 
            // https://jira.maxim-ic.com/browse/OS24EVK-59 WndProc if HID.WM_DEVICECHANGE do HID.OnDeviceChange(Message m)
            try
            {
                if (m.Msg == HID.WM_DEVICECHANGE)
                {
                    //if ((int)m.WParam == HID.DBT_DEVNODES_CHANGED)  //this always occurs when any USB device is attached/detached. Use this if not registering for device notifications.
                    if (
                            (   m.WParam.ToInt32() == HID.DBT_DEVICEARRIVAL 
                            ||  m.WParam.ToInt32() == HID.DBT_DEVICEREMOVECOMPLETE
                            ) 
                        && 
                            (m.LParam.ToInt32() != 0) 
                        && 
                            DeviceIDMatch(m)
                       )
                    {
                        closeHIDhandles();
                        findHIDs();
                        // cboEVB_init();
                        return true;
                    }
                }
                return false;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        /// <summary>
        /// findHIDs() called upon startup or if a desired HID has been inserted or removed
        /// </summary>
        public void findHIDs()
        {
            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
            ArrayList allHIDPathNameArray = new ArrayList();
            System.Guid hidGuid = new System.Guid();

            // https://jira.maxim-ic.com/browse/OS24EVK-59 (intermittent) Matlab Exception Message: Arithmetic operation resulted in an overflow.
#if DEBUG
#else
            try
            {
#endif
                HidD_GetHidGuid(ref hidGuid);
                // FindDesiredHIDPathNamesFromGuid() builds desiredHIDPathNameArray which is a list of all desired HIDs in the system
                // desiredHIDPathNameArray is a global list and is used to maintain the order of desired HIDs in the selection list. 
                // USB ports have different priorities, and the user could have attached the first desired HID to a lower priority port. 
                // Hence, when another desired HID is attached to a higher priority port, it will come earlier in allHIDPathNameArray, but it will be maintained in the same attachment order in desiredHIDPathNameArray.
                // Note that desiredHIDPathNameArray is only appended to or deleted from; it is never recreated in whole.
                // FIXME desiredHIDPathNameArray will get duplicate pathnames for desired HIDs that were already attached, but these will be removed by openHIDhandles() since they're in allHIDPathNameArray only once. 
                // These duplicates pathnames are appended after the initial list, so they won't affect order.
                FindDesiredHIDPathNamesFromGuid(hidGuid);
                // FindAllHIDPathNamesFromGuid() builds allHIDPathNameArray which is a list of all HIDs in the system. It is recreated every time a desired HID is attached or removed.
                FindAllHIDPathNamesFromGuid(hidGuid, allHIDPathNameArray);
                // openHIDhandles() gets handles for all desired HIDs
                // openHIDhandles() loops through all attached HIDs and checks for a match of each item in desiredHIDPathNameArray. This maintains the attachement order.
                // If a previously attached HID has been removed, it won't be found in allHIDPathNameArray and it will be removed from desiredHIDPathNameArray.
                openHIDhandles(allHIDPathNameArray);
                if (desiredHIDPathNameArray.Count != 0)
                {
                    prepareForOverlappedTransfer();
                }
#if DEBUG
#else
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
#endif
            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        private void openHIDhandles(ArrayList allHIDPathNameArray)
        {
            DebugMessage = string.Format("{0} entered", System.Reflection.MethodInfo.GetCurrentMethod().Name);
            int desiredHIDPathNameArrayCounter;
            int allHIDPathNameArrayCounter;
            bool found_installed_device;
            try
            {
                desiredHIDPathNameArrayCounter = 0;
                while (desiredHIDPathNameArrayCounter < desiredHIDPathNameArray.Count) // count will change if a previously installed device has been removed, so don't use a for loop
                {
                    found_installed_device = false;
                    allHIDPathNameArrayCounter = 0;
                    while (allHIDPathNameArrayCounter < allHIDPathNameArray.Count)
                    {
                        if ((string)allHIDPathNameArray[allHIDPathNameArrayCounter] == (string)desiredHIDPathNameArray[desiredHIDPathNameArrayCounter])
                        {
                            writeHandle = FileIO.CreateFile((string)desiredHIDPathNameArray[desiredHIDPathNameArrayCounter], FileIO.GENERIC_READ | FileIO.GENERIC_WRITE, FileIO.FILE_SHARE_READ | FileIO.FILE_SHARE_WRITE, IntPtr.Zero, FileIO.OPEN_EXISTING, 0, 0);
                            if (!(writeHandle.IsInvalid))
                            {
                                readHandle = FileIO.CreateFile((string)desiredHIDPathNameArray[desiredHIDPathNameArrayCounter], FileIO.GENERIC_READ | FileIO.GENERIC_WRITE, FileIO.FILE_SHARE_READ | FileIO.FILE_SHARE_WRITE, IntPtr.Zero, FileIO.OPEN_EXISTING, FileIO.FILE_FLAG_OVERLAPPED, 0);
                                if (!(readHandle.IsInvalid))
                                {
                                    writeHandleArray.Add(writeHandle);
                                    readHandleArray.Add(readHandle);
                                    allHIDPathNameArray.RemoveAt(allHIDPathNameArrayCounter); // remove it so we don't repeatedly add it when we check for new devices (after we finish checking for previously installed devices)
                                    found_installed_device = true;
                                }
                                else
                                {
                                    writeHandle.Close();
                                    writeHandle = null;
                                    readHandle = null;
                                }
                            }
                            else
                            {
                                writeHandle = null;
                                readHandle = null; 
                            }
                            break;
                        }
                        allHIDPathNameArrayCounter++;
                    }
                    if (found_installed_device == false)    // no match in all allHIDPathNameArray elements; the device has been removed so remove its pathname from desiredHIDPathNameArray. Don't use counter at max count to check if not found, since max count can change.
                        desiredHIDPathNameArray.RemoveAt(desiredHIDPathNameArrayCounter);   // decrements count by 1; don't increment desiredHIDPathNameArrayCounter
                    else
                        desiredHIDPathNameArrayCounter++;
                }
            }
            catch (Exception ex)
            {
                DebugMessage = string.Format("{0} exception {1}", System.Reflection.MethodInfo.GetCurrentMethod().Name, ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
            DebugMessage = string.Format("{0} exited", System.Reflection.MethodInfo.GetCurrentMethod().Name);
        }

        public void closeHIDhandles()
        {
            try
            {
                if (desiredHIDPathNameArray.Count != 0)
                {
                    foreach (Object obj in writeHandleArray)
                        ((SafeFileHandle)obj).Close();
                    writeHandleArray.Clear();
                    writeHandle = null;
                    foreach (Object obj in readHandleArray)
                        ((SafeFileHandle)obj).Close();
                    readHandleArray.Clear();
                    readHandle = null;
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        private void prepareForOverlappedTransfer()
        {
            try
            {
                EventObject = FileIO.CreateEvent(IntPtr.Zero, false, false, String.Empty);
                managedOverlapped.OffsetLow = 0;
                managedOverlapped.OffsetHigh = 0;
                managedOverlapped.EventHandle = EventObject; // HIDOverlapped is the overlapped structure used in ReadFile; EventObject will be signaled upon completion of ReadFile
                nonManagedOverlapped = Marshal.AllocHGlobal(Marshal.SizeOf(managedOverlapped));
                Marshal.StructureToPtr(managedOverlapped, nonManagedOverlapped, false);

                nonManagedBuffer = Marshal.AllocHGlobal(REPORT_SIZE);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void FlushQueue()
        {
            try
            {
                if (writeHandle == null)
                {
                    return;
                }
                if (readHandle == null)
                {
                    return;
                }

                HidD_FlushQueue(writeHandle);
                HidD_FlushQueue(readHandle);
            }
            catch
            {
                throw new Exception(new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void writeReadHID()
        {
            try
            {
                if (writeHandle == null)
                {
                    return;
                }
                if (readHandle == null)
                {
                    return;
                }

                writeHID();
                readHID();
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void writeHID()
        {
            if (writeHandle == null)
            {
                return;
            }
            if (readHandle == null)
            {
                return;
            }

            int BytesSucceed;
            bool api_status;

            try
            {
                BytesSucceed = 0;
                Marshal.Copy(IOBuf, 0, nonManagedBuffer, REPORT_SIZE);
                api_status = FileIO.WriteFile(writeHandle, nonManagedBuffer, REPORT_SIZE, ref BytesSucceed, IntPtr.Zero);
                //endpoint in interrupt at uC occurs after this WriteFile call since the in data is ready and the host takes it; endpoint in interrupt does not occur after ReadFile call

                if (api_status == false)
                {
                    //MessageBox.Show(Err.LastDllError);
                    throw new Exception(support.ResultOfAPICall("API WriteFile error"));
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void readHID()
        {
            if (writeHandle == null)
            {
                return;
            }
            if (readHandle == null)
            {
                return;
            }

            int BytesSucceed;
            bool api_status;
            int status;

            try
            {
                Array.Clear(IOBuf, 0, IOBuf.Length);

                BytesSucceed = 0;

                api_status = FileIO.ReadFile(readHandle, nonManagedBuffer, REPORT_SIZE, ref BytesSucceed, nonManagedOverlapped);
                
                if (api_status == false) 
                {
                    //MsgBox(Err.LastDllError)
                    status = FileIO.WaitForSingleObject(EventObject, IOtimeout);                    

                    if (status != FileIO.WAIT_OBJECT_0) 
                    {
                        api_status = FileIO.CancelIo(readHandle);
                        throw new Exception(support.ResultOfAPICall("API ReadFile error"));
                    }
                    FileIO.GetOverlappedResult(readHandle, nonManagedOverlapped, ref BytesSucceed, false);
                }

                // TODO1: OS24EVK-57 2015-04-01 myHID.readHID2() needs to return result into IOBuf. But IOBuf = new byte[64], do we replace with IOBuf = new byte[128]? Does this affect readHID()?
                if (BytesSucceed > IOBuf.Length)
                {
                    IOBuf = new byte[BytesSucceed];
                    Array.Clear(IOBuf, 0, IOBuf.Length);
                }
                Marshal.Copy(nonManagedBuffer, IOBuf, 0, BytesSucceed);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void readHID2() // this function is for testing multiple report read on a single ReadFile()
        {
            if (writeHandle == null)
            {
                return;
            }
            if (readHandle == null)
            {
                return;
            }

            int BytesSucceed;
            bool api_status;
            int status;

            try
            {
                int size = 128;
                //byte[] buf = new byte[size];
                IntPtr nonManagedBuf = Marshal.AllocHGlobal(size);
                //Array.Clear(buf, 0, size);

                BytesSucceed = 0;

                api_status = FileIO.ReadFile(readHandle, nonManagedBuf, size, ref BytesSucceed, nonManagedOverlapped);

                if (api_status == false)
                {
                    //MsgBox(Err.LastDllError)
                    status = FileIO.WaitForSingleObject(EventObject, IOtimeout);

                    if (status != FileIO.WAIT_OBJECT_0)
                    {
                        api_status = FileIO.CancelIo(readHandle);
                        throw new Exception(support.ResultOfAPICall("API ReadFile error"));
                    }
                    FileIO.GetOverlappedResult(readHandle, nonManagedOverlapped, ref BytesSucceed, false);
                }

                // TODO1: OS24EVK-57 2015-04-01 myHID.readHID2() needs to return result into IOBuf. But IOBuf = new byte[64], do we replace with IOBuf = new byte[128]? Does this affect readHID()?
                if (BytesSucceed > IOBuf.Length)
                {
                    IOBuf = new byte[BytesSucceed];
                    Array.Clear(IOBuf, 0, IOBuf.Length);
                }
                Marshal.Copy(nonManagedBuf, IOBuf, 0, BytesSucceed);
                
                if (nonManagedBuf != IntPtr.Zero)
                    Marshal.FreeHGlobal(nonManagedBuffer);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }
        public void freeHeap()
        {
            try
            {
                if (nonManagedBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(nonManagedBuffer);
                if (nonManagedOverlapped != IntPtr.Zero)
                    Marshal.FreeHGlobal(nonManagedOverlapped);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        ///  <summary>
        ///  Requests to receive a notification when a device is attached or removed.
        ///  </summary>
        ///  
        ///  <param name="formHandle"> handle to the window that will receive device events. </param>
        ///  <param name="classGuid"> device interface GUID. </param>
        ///  <param name="deviceNotificationHandle"> returned device notification handle. </param>
        ///  
        ///  <returns>
        ///  True on success.
        ///  </returns>
        ///  
        public Boolean RegisterForDeviceNotifications(IntPtr formHandle, Guid classGuid, ref IntPtr deviceNotificationHandle)
        {
            DEV_BROADCAST_DEVICEINTERFACE devBroadcastDeviceInterface = new DEV_BROADCAST_DEVICEINTERFACE();
            IntPtr devBroadcastDeviceInterfaceBuffer = IntPtr.Zero;
            Int32 size = 0;

            try
            {
                size = Marshal.SizeOf(devBroadcastDeviceInterface);
                devBroadcastDeviceInterface.dbcc_size = size;
                devBroadcastDeviceInterface.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
                devBroadcastDeviceInterface.dbcc_reserved = 0;
                devBroadcastDeviceInterface.dbcc_classguid = classGuid;
                
                // Allocate memory for the buffer that holds the DEV_BROADCAST_DEVICEINTERFACE structure.
                devBroadcastDeviceInterfaceBuffer = Marshal.AllocHGlobal(size);

                // Copy the DEV_BROADCAST_DEVICEINTERFACE structure to the buffer.
                // Set fDeleteOld True to prevent memory leaks.
                Marshal.StructureToPtr(devBroadcastDeviceInterface, devBroadcastDeviceInterfaceBuffer, true);

                // ***
                //  API function

                //  summary
                //  Request to receive notification messages when a device in an interface class
                //  is attached or removed.

                //  parameters 
                //  Handle to the window that will receive device events.
                //  Pointer to a DEV_BROADCAST_DEVICEINTERFACE to specify the type of 
                //  device to send notifications for.
                //  DEVICE_NOTIFY_WINDOW_HANDLE indicates the handle is a window handle.

                //  Returns
                //  Device notification handle or NULL on failure.
                // ***

                deviceNotificationHandle = RegisterDeviceNotification(formHandle, devBroadcastDeviceInterfaceBuffer, DEVICE_NOTIFY_WINDOW_HANDLE);

                // Marshal data from the unmanaged block devBroadcastDeviceInterfaceBuffer to the managed object devBroadcastDeviceInterface
                // why?
                Marshal.PtrToStructure(devBroadcastDeviceInterfaceBuffer, devBroadcastDeviceInterface);

                return deviceNotificationHandle.ToInt32() == IntPtr.Zero.ToInt32() ? false : true;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
            finally
            {
                // Free the memory allocated previously by AllocHGlobal.
                if (devBroadcastDeviceInterfaceBuffer != IntPtr.Zero)
                    Marshal.FreeHGlobal(devBroadcastDeviceInterfaceBuffer);                
            }
        }

        ///  <summary>
        ///  Requests to stop receiving notification messages when a device in an
        ///  interface class is attached or removed.
        ///  </summary>
        ///  
        ///  <param name="deviceNotificationHandle"> handle returned previously by
        ///  RegisterDeviceNotification. </param>

        public void StopReceivingDeviceNotifications(IntPtr deviceNotificationHandle)
        {
            try
            {
               UnregisterDeviceNotification(deviceNotificationHandle);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public Boolean DeviceIDMatch(Message m)
        {
            Int32 stringSize;

            try
            {
                DEV_BROADCAST_HDR devBroadcastHeader = new DEV_BROADCAST_HDR(); 
                DEV_BROADCAST_DEVICEINTERFACE_1 devBroadcastDeviceInterface = new DEV_BROADCAST_DEVICEINTERFACE_1();

                // The LParam parameter of Message is a pointer to a DEV_BROADCAST_HDR structure.
                Marshal.PtrToStructure(m.LParam, devBroadcastHeader);
                if ((devBroadcastHeader.dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE))
                {
                    // The dbch_devicetype parameter indicates that the event applies to a device interface.
                    // So the structure in LParam is actually a DEV_BROADCAST_INTERFACE structure, 
                    // which begins with a DEV_BROADCAST_HDR.

                    // Obtain the number of characters in dbch_name by subtracting the 32 bytes
                    // in the strucutre that are not part of dbch_name and dividing by 2 because there are 
                    // 2 bytes per character.

                    stringSize = System.Convert.ToInt32((devBroadcastHeader.dbch_size - 32) / 2);

                    // The dbcc_name parameter of devBroadcastDeviceInterface contains the device name. 
                    // Trim dbcc_name to match the size of the String.         

                    devBroadcastDeviceInterface.dbcc_name = new Char[stringSize + 1];

                    // Marshal data from the unmanaged block pointed to by m.LParam 
                    // to the managed object devBroadcastDeviceInterface.

                    Marshal.PtrToStructure(m.LParam, devBroadcastDeviceInterface);

                    // Store the device name in a String.

                    String DeviceNameString = new String(devBroadcastDeviceInterface.dbcc_name, 0, stringSize);

                    // Compare the name of the newly attached device with the name of the device 
                    // the application is accessing (deviceID).
                    // Set ignorecase True.

                    return (DeviceNameString.ToLower().IndexOf(deviceID.ToLower()) == -1) ? false : true;
                }
                else
                    return false;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void reportID()
        {
            try
            {
                if (explicit_report_id)
                    IOBuf[0] = SHORT_REPORT_ID;     // explicit out report ID in HID's descriptor
                else
                    IOBuf[0] = DEFAULT_REPORT_ID;   // default report ID; this byte is dropped in transfer, so we don't really waste a byte transmitting it
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public int readI2C(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] readData, byte[] reg, bool ignoreNACK = false)
        {
            //readData is passed back to caller with the read data
            int status;
            int i;

            try
            {
                if (numDataBytes > REPORT_SIZE - 2) {
                    throw new Exception("USB buffer overflow");
                }

                // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in readI2C
                mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
                FlushQueue();
                Array.Clear(IOBuf, 0, IOBuf.Length);

                reportID();
                IOBuf[1] = 6;	// I2C transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
                IOBuf[2] = (byte)(deviceAddress | 1);     // set read bit
                IOBuf[3] = numDataBytes;
                IOBuf[4] = numRegBytes;
                // TODO1: OS24EVK-57 HID readI2C no validation that numRegBytes == reg.Length ?
                for (i = 0; i < numRegBytes; i++) {
                    IOBuf[i + 5] = reg[i];
                }

                writeHID();
                mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.

                status = checkNACK();

                // checkNACK also reads the data, assuming NACK didn't occur
                if (status == I2C_NACK_ERROR && ignoreNACK == false) {
                    throw new Exception("invalid I2C address");
                }

                for (i = 0; i < numDataBytes; i++) {
                    readData[i] = IOBuf[i + 2];
                }

                return status;      // the caller will not need the return value if an exception is thrown
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        public void writeI2C(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] data, byte[] reg, bool ignoreNACK = false)
        {
            int i;

            try
            {
                if (numDataBytes > REPORT_SIZE - 5) {
                    throw new Exception("USB buffer overflow");
                }

                // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in writeI2C
                mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
                FlushQueue();
                Array.Clear(IOBuf, 0, IOBuf.Length);

                //send data to tell uC to do I2C read from slave
                reportID();
                IOBuf[1] = 6;	// I2C transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
                IOBuf[2] = deviceAddress;
                IOBuf[3] = numDataBytes;
                IOBuf[4] = numRegBytes;
                for (i = 0; i < numRegBytes; i++) {
                    IOBuf[i + 5] = reg[i];
                }
                for (i = 0; i < numDataBytes; i++) {
                    IOBuf[i + 5 + numRegBytes] = data[i];
                }

                writeHID();
                mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.

                if (checkNACK() == I2C_NACK_ERROR && ignoreNACK == false) {
                    throw new Exception("invalid I2C address");
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        private int checkNACK()
        {
            // Check if I2C write was really succesful or a NACK occurred, since this is independent of USB success
            // This function also reads all the data from the report. If NACK occured, the data is invalid.
            try
            {
                readHID();
                return (IOBuf[1] == 0 ? I2C_SUCCESS : I2C_NACK_ERROR);    // the caller will not need the return value if an exception is thrown
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        // only allows 2 byte data
        public void setBitI2C(byte deviceAddress, int reg, byte range, int data, bool ignoreNACK = false)
        {
            int i;
            
            try
            {
                int LSb = range & 0xF;
                int MSb = (range & 0xF0) >> 4;
                if (MSb < LSb)
                    throw new Exception("invalid bit range");
                //if (numDataBytes > REPORT_SIZE - 5)
                //    throw new Exception("USB buffer overflow");

                // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in setBitI2C
                mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
                FlushQueue();

                Array.Clear(IOBuf, 0, IOBuf.Length);

                //send data to tell uC to do I2C read from slave
                reportID();
                IOBuf[1] = 6;	// I2C transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
                IOBuf[2] = deviceAddress;
                byte numDataBytes = (byte)(MSb > 7 ? 2 : 1);
                IOBuf[3] = numDataBytes;
                byte numRegBytes = (byte)Math.Ceiling(reg / 255.0);
                IOBuf[4] = numRegBytes;
                for (i = 0; i < numRegBytes; i++)                
                    IOBuf[i + 5] = (byte)((reg>>(8*i)) & 0xFF);                
                //for (i = 0; i < numDataBytes; i++)
                //    IOBuf[i + 5 + numRegBytes] = data[i];

                writeHID();
                mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.

                if (checkNACK() == I2C_NACK_ERROR && ignoreNACK == false)
                    throw new Exception("invalid I2C address");
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message + Environment.NewLine + new System.Diagnostics.StackFrame().GetMethod().Name);
            }
        }

        /// <summary>
        /// <para>Firmware command code 0 IN = Firmware version</para>
        /// 
        /// </summary>
        /// <param name="VerMajor"></param>
        /// <param name="VerMinor"></param>
        /// <param name="verYearHundreds"></param>
        /// <param name="verYear"></param>
        /// <param name="verMonth"></param>
        /// <param name="verDay"></param>
        public void FirmwareVersion(out byte VerMajor, out byte VerMinor, out byte verYearHundreds, out byte verYear, out byte verMonth, out byte verDay)
        {
            if (FirmwareINT0Enabled != 0)
            {
                // firmware is busy streaming out HID reports,
                // so return a previously retrieved firmware version.
                VerMajor = _VerMajor;
                VerMinor = _VerMinor;
                verYearHundreds = _verYearHundreds;
                verYear = _verYear;
                verMonth = _verMonth;
                verDay = _verDay;
                return;
            }
            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in FirmwareVersion
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 0; // Firmware version -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            writeReadHID();
            VerMajor = IOBuf[2];
            VerMinor = IOBuf[3];
            verYearHundreds = IOBuf[4];
            verYear = IOBuf[5];
            verMonth = IOBuf[6];
            verDay = IOBuf[7];
            //
            // save the recently read values
            _VerMajor = VerMajor;
            _VerMinor = VerMinor;
            _verYearHundreds = verYearHundreds;
            _verYear = verYear;
            _verMonth = verMonth;
            _verDay = verDay;
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }
        private byte _VerMajor = 0;
        private byte _VerMinor = 0;
        private byte _verYearHundreds = 0;
        private byte _verYear = 0;
        private byte _verMonth = 0;
        private byte _verDay = 0;

        /// <summary>
        /// <para>Firmware command code 1 OUT = LED (C51F321 P2.2=red, P2.1=green) </para>
        /// 
        /// </summary>
        /// <param name="xxxxxxP22P21">0x02=P2.2 Red, 0x01=P2.1 Green</param>
        public void LEDSet(byte xxxxxxP22P21)
        {
            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers LEDSet(byte xxxxxxP22P21)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in LEDSet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 1; // case (1): //LED (C51F321 P2.2=red, P2.1=green) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 0; //    case (0):	// write
            IOBuf[3] = xxxxxxP22P21; 
            // Led2 = IO_BUFFER.Ptr[2+gOffset] & 1; // sbit Led2 = P2^1;
            // Led1 = (IO_BUFFER.Ptr[2+gOffset] & 2) >> 1; // sbit Led1 = P2^2;
            writeHID();
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 1 IN = GPIOP0Get read I/O pins P0.6, P0.7</para>
        /// 
        /// </summary>
        /// <param name="xxxxxxP06P07"></param>
        public void GPIOP0Get(out byte xxxxxxP06P07)
        {
            // todo: verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP0Get(out byte xxxxxxP06P07)
            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: Rename LEDGet(out byte xxxxxxP06P07) to GPIOP0Get(out byte xxxxxxP06P07)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP0Get
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 1; // case (1): //LED (C51F321 P2.2=red, P2.1=green) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 1; //    case (1):	// read
            writeReadHID();
            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
            // IO_BUFFER.Ptr[0 + gOffset] = 0; // error status
            // IO_BUFFER.Ptr[1 + gOffset] = ((P0 & 0x80) >> 7) + ((P0 & 0x40) >> 5);
            xxxxxxP06P07 = IOBuf[2];
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 2 OUT = GPIO configuration (C51F321 P1 and P2) </para>
        /// 
        /// </summary>
        /// <param name="P1MDOUT"></param>
        /// <param name="P2MDOUT"></param>
        /// <param name="weakPullupDisable"></param>
        public void GPIOP1P2ConfigSet(byte P1MDOUT, byte P2MDOUT, byte weakPullupDisable)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2ConfigSet(byte P1MDOUT, byte P2MDOUT, byte weakPullupDisable)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2ConfigSet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 2; // case (2): //GPIO configuration (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 0; //    case (0):	// write
            IOBuf[3] = P1MDOUT; // P1MDOUT = IO_BUFFER.Ptr[2+gOffset];	// P1 push-pull (1) or open-collector (0)
            IOBuf[4] = P2MDOUT; // P2MDOUT = IO_BUFFER.Ptr[3 + gOffset];	// P2 push-pull (1) or open-collector (0)
            IOBuf[5] = weakPullupDisable; // (IO_BUFFER.Ptr[4 + gOffset] & 1) ? (XBR1 |= 0x80) : (XBR1 &= 0x7F);	// weak pull up disable (open collector only); 1 disabled, 0 enabled
            writeHID();
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 2 IN = GPIO configuration (C51F321 P1 and P2) </para>
        /// 
        /// </summary>
        /// <param name="P1MDOUT"></param>
        /// <param name="P2MDOUT"></param>
        /// <param name="weakPullupDisable"></param>
        public void GPIOP1P2ConfigGet(out byte P1MDOUT, out byte P2MDOUT, out byte weakPullupDisable)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2ConfigGet(out byte P1MDOUT, out byte P2MDOUT, out byte weakPullupDisable)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2ConfigGet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 2; // case (2): //GPIO configuration (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 1; //    case (1):	// read
            writeReadHID();
            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
            // IO_BUFFER.Ptr[0 + gOffset] = 0; // error status					
            // IO_BUFFER.Ptr[1 + gOffset] = P1MDOUT;	// P1 push-pull (1) or open-collector (0)
            // IO_BUFFER.Ptr[2 + gOffset] = P2MDOUT;	// P2 push-pull (1) or open-collector (0)
            // IO_BUFFER.Ptr[3 + gOffset] = (XBR1 & 0x80) >> 7; // weakPullupDisable
            P1MDOUT = IOBuf[2];
            P2MDOUT = IOBuf[3];
            weakPullupDisable = IOBuf[4];
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 3 OUT = GPIO value (C51F321 P1 and P2) </para>
        /// 
        /// </summary>
        /// <param name="P1"></param>
        /// <param name="P2"></param>
        public void GPIOP1P2Out(byte P1, byte P2)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2Out(byte P1, byte P2)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2Out
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 3; // case (3): //GPIO value (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 0; //    case (0):	// write
            IOBuf[3] = P1; // P1 = IO_BUFFER.Ptr[2 + gOffset];		// P1 HI (1) or LO (0); set P1==1 and P1MDOUT==1 for HI-Z
            IOBuf[4] = P2; // P2 = IO_BUFFER.Ptr[3 + gOffset];		// P2 HI (1) or LO (0); set P2==1 and P1MDOUT==2 for HI-Z
            writeHID();
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 3 IN = GPIO value (C51F321 P1 and P2) </para>
        /// 
        /// </summary>
        /// <param name="P1"></param>
        /// <param name="P2"></param>
        public void GPIOP1P2In(out byte P1, out byte P2)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers GPIOP1P2In(out byte P1, out byte P2)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in GPIOP1P2In
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 3; // case (3): //GPIO value (C51F321 P1 and P2) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 1; //    case (1):	// read
            writeReadHID();
            // temp = XBR1;
            // if (IO_BUFFER.Ptr[2+gOffset] & 1)	// enable weak pullups in case GP pins are open-collector and not connected to anything (which would falsely give '0')
            // {
            // 	XBR1 &= 0x7F;
            // 	Timer0_Init(HALFMS);
            // 	T0_Wait(2);
            // }
            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
            // IO_BUFFER.Ptr[0+gOffset] = 0; 		// error status
            // IO_BUFFER.Ptr[1+gOffset] = P1;		// P1 HI (1) or LO (0)
            // IO_BUFFER.Ptr[2+gOffset] = P2;		// P2 HI (1) or LO (0)
            // XBR1 = temp;
            // SendPacket(); // no need to check for internal call since only the GUI will request a read through this function
            P1 = IOBuf[2];
            P2 = IOBuf[3];
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 4 OUT = I2C configuration </para>
        ///  (IO_BUFFER.Ptr[3+gOffset] &amp; 1) ? (SMB0CF |= 0x10) : (SMB0CF &amp;= 0xEF)
        /// </summary>
        /// <param name="gI2Cflags">bit0 == 1: repeated start</param>
        /// <param name="EXTHOLD">set EXTHOLD bit (SMBus setup / hold time extension)</param>
        /// <param name="ClearSDAbyTogglingSCL">clear SDA by toggling SCL</param>
        public void I2CConfigSet(byte gI2Cflags, byte EXTHOLD, byte ClearSDAbyTogglingSCL)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CConfigSet(byte gI2Cflags, byte EXTHOLD, byte ClearSDAbyTogglingSCL)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CConfigSet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 4; // case (4): //I2C configuration -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 0; //    case (0):	// write
            IOBuf[3] = gI2Cflags;   // gI2Cflags = IO_BUFFER.Ptr[2+gOffset];
                                    // // bit0 == 1: repeated start (versus stop/start) after write before read (applies to random read only)
                                    // // bit1 == 1: start random read with a write, but end the write right away without sending reg address (emulate Jungo dongle for debug purposes)
                                    // // bit2 == 1: repeat transaction if slave NACKs (suggest not to use this)
                                    // // all flags are OR'd
            IOBuf[4] = EXTHOLD;    // (IO_BUFFER.Ptr[3+gOffset] & 1) ? (SMB0CF |= 0x10) : (SMB0CF &= 0xEF);	// set EXTHOLD bit (SMBus setup / hold time extension)
            IOBuf[5] = ClearSDAbyTogglingSCL;   // if (IO_BUFFER.Ptr[4+gOffset] & 1)	// clear SDA by toggling SCL
                                // {s
                                // 	IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
                                // 	IO_BUFFER.Ptr[0+gOffset] = 0; 	// transaction error status
                                // 	IO_BUFFER.Ptr[1+gOffset] = clearSDA();	// clearSDA error status
                                // 	SendPacket();			// send status of clearing SDA to host
                                // }
            writeHID();
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 4 IN = I2C configuration </para>
        /// (IO_BUFFER.Ptr[3+gOffset] &amp; 1) ? (SMB0CF |= 0x10) : (SMB0CF &amp;= 0xEF)
        /// </summary>
        /// <param name="gI2Cflags">bit0 == 1: repeated start</param>
        /// <param name="EXTHOLD">SMB0CF &amp; 0x10</param>
        public void I2CConfigGet(out byte gI2Cflags, out byte EXTHOLD)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CConfigGet(out byte gI2Cflags, out byte EXTHOLD)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CConfigGet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 4; // case (4): //I2C configuration -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 1; //    case (1):	// read
            writeReadHID();
            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
            // IO_BUFFER.Ptr[0+gOffset] = 0; // error status					
            // IO_BUFFER.Ptr[1+gOffset] = gI2Cflags;
            // IO_BUFFER.Ptr[2+gOffset] = (SMB0CF & 0x10) >> 4; // EXTHOLD
            gI2Cflags = IOBuf[2];
            EXTHOLD = IOBuf[3];
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 5 OUT = I2C clock rate (number of counts to overflow) </para>
        /// <para>SCL_kHz = TimerClockMHz * 1000 / ReloadTH1</para>
        /// </summary>
        /// <param name="ReloadTH1">SMBus timer's count value: ReloadTH1 = (byte)(0.5 + (TimerClockMHz * 1000 / SCL_kHz))</param>
        public void I2CClockSet(byte ReloadTH1)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CClockSet(byte ReloadTH1)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CClockSet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 5; // case (5): //I2C clock rate (number of counts to overflow) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 0; //    case (0):	// write
            IOBuf[3] = ReloadTH1;
            // gSMBusClkFreq = gTimer1ClkFreq / 3.0 / IO_BUFFER.Ptr[2+gOffset];	//GUI sends the number of counts to overflow; HID must calculate the desired SMBus clock frequency
            // TR1 = 0;
            // Timer1_Init();
            writeHID();
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 5 IN = I2C clock rate (number of counts to overflow) </para>
        /// <para>SCL_kHz = TimerClockMHz * 1000 / ReloadTH1</para>
        /// </summary>
        /// <param name="TimerClockMHz">SMBus timer's clock frequency (in MHz); expect constant 8</param>
        /// <param name="ReloadTH1">SMBus timer's count value: SCL_kHz = TimerClockMHz * 1000 / ReloadTH1</param>
        public void I2CClockGet(out byte TimerClockMHz, out byte ReloadTH1)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers I2CClockGet(out byte TimerClockMHz, out byte ReloadTH1)
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CClockGet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 5; // case (5): //I2C clock rate (number of counts to overflow) -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = 1; //    case (1):	// read
            writeReadHID();
            // case (1): 	// read -- myHID.I2CClockGet(out TimerClockMHz, out ReloadTH1)
            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
            // IO_BUFFER.Ptr[0+gOffset] = 0; // error status
            // IO_BUFFER.Ptr[1+gOffset] = gTimer1ClkFreq / 1000000 / 3;	//return the SMBus timer's clock frequency (in MHz)
            // IO_BUFFER.Ptr[2+gOffset] = 256-TH1;						//and return the SMBus timer's count value in order to calculate the SMBus clock frequency; TH1 is the reload value that gets loaded into TL0 upon overflow; the reload value is 256-TH1 since (0)-TH1 gives the proper number of counts to overflow
            // SendPacket(); // no need to check for internal call since only the GUI will request a read through this function
            // break;
            TimerClockMHz = IOBuf[2];
            ReloadTH1 = IOBuf[3];
            // todo: OS24EVK-24 how determine SCL from TimerClockMHZ? Observed I2CClockGet() TimerClockMHz = 8 when SCL = 400kHz.
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 6 OUT = I2C transaction </para>
        /// 
        /// </summary>
        /// <param name="deviceAddress">I2C device address, 8-bit left-justified (LSB=R/w bit)</param>
        /// <param name="numDataBytes"></param>
        /// <param name="numRegBytes"></param>
        /// <param name="data"></param>
        /// <param name="reg"></param>
        /// <param name="ignoreNACK"></param>
        public void I2CWrite(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] data, byte[] reg, bool ignoreNACK = false)
        {
            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
            // alias of existing function
            // IOBuf[1] = 6; // case (6):	// I2C transaction -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            writeI2C(deviceAddress, numDataBytes, numRegBytes, data, reg, ignoreNACK);
        }

        /// <summary>
        /// <para>Firmware command code 6 IN = I2C transaction </para>
        /// 
        /// </summary>
        /// <param name="deviceAddress">I2C device address, 8-bit left-justified (LSB=R/w bit)</param>
        /// <param name="numDataBytes"></param>
        /// <param name="numRegBytes"></param>
        /// <param name="readData"></param>
        /// <param name="reg"></param>
        /// <param name="ignoreNACK"></param>
        /// <returns>status value; may be HID.I2C_NACK_ERROR if ignoreNACK parameter is true</returns>
        public int I2CRead(byte deviceAddress, byte numDataBytes, byte numRegBytes, byte[] readData, byte[] reg, bool ignoreNACK = false)
        {
            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
            // alias of existing function
            // IOBuf[1] = 6; // case (6):	// I2C transaction -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            return readI2C(deviceAddress, numDataBytes, numRegBytes, readData, reg, ignoreNACK);
        }

        /// <summary>
        /// <para>Firmware command code 7 OUT = SPI config </para>
        /// ((SPI0CFG &amp; 0x30) &gt;&gt; 4) + (SPI0CN &amp; 4)
        /// </summary>
        /// <param name="config"></param>
        public void SPIConfigSet(byte config)
        {
            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIConfigSet(byte config)
            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
            // IOBuf[1] = 7; // case (7):	// SPI config -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
        }

        /// <summary>
        /// <para>Firmware command code 7 IN = SPI config </para>
        /// ((SPI0CFG &amp; 0x30) &gt;&gt; 4) + (SPI0CN &amp; 4)
        /// </summary>
        /// <param name="config"></param>
        public void SPIConfigGet(out byte config)
        {
            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIConfigGet(out byte config)
            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
            // IOBuf[1] = 7; // case (7):	// SPI config -- HID.cs void writeI2C() readI2C() -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            config = 0;
        }

        /// <summary>
        /// <para>Firmware command code 8 OUT = SPI clock rate </para>
        /// </summary>
        /// <param name="SPI0CKR"></param>
        public void SPIClockSet(byte SPI0CKR)
        {
            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIClockSet(byte SPI0CKR)
            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
            // IOBuf[1] = 8; // case (8): 	// SPI clock rate -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
        }

        /// <summary>
        /// <para>Firmware command code 8 IN = SPI clock rate </para>
        /// </summary>
        /// <param name="SPI0CKR"></param>
        public void SPIClockGet(out byte SPI0CKR)
        {
            // todo: https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers SPIClockGet(out byte SPI0CKR)
            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
            // IOBuf[1] = 8; // case (8): 	// SPI clock rate -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            SPI0CKR = 0;
        }

        /// <summary>
        /// <para>Firmware command code 9 = SPI transaction </para>
        /// </summary>
        /// <param name="mosiData">SPI MOSI (Master-Out, Slave-In) data to write into slave device</param>
        /// <param name="misoBuffer">SPI MISO (Master-In, Slave-Out) data buffer containing data bytes received from slave device
        /// (size will be allocated same size as mosiData)</param>
        public void SPITransfer(byte[] mosiData, out byte[] misoBuffer)
        {
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-24 SPITransfer(byte[] mosiData, out byte[] misoBuffer)
            // Although the MAX30101EVKIT firmware will accept HID SPI commands, this firmware doesn't support SPI interface.
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in I2CConfigSet
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            // assign default out values in case of failure
            int byteCount = mosiData.Length;
            byte num_bytes = (byte)(byteCount & 0xFF);
            misoBuffer = new byte[byteCount];
            for (int byteIndex = 0; byteIndex < byteCount; byteIndex++)
            {
                // initial dummy data
                misoBuffer[byteIndex] = 0x55;
            }
            IOBuf[1] = 9; // case (9):	// SPI transaction -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            //IOBuf[8] = x; // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
            for (uint byteIndex = 0; byteIndex < num_bytes; byteIndex++)
            {
                IOBuf[8 + byteIndex] = (byte)(mosiData[byteIndex]);
            }
            //switch (SPImode)
            //{
            //    case 0:
                    IOBuf[2] = 0; // IO_BUFFER.Ptr[1+gOffset] -- 0 for SPI_mode0: multi-byte SPI transfer.
                    IOBuf[3] = 0; // IO_BUFFER.Ptr[2+gOffset] -- not used
                    IOBuf[4] = 0; // IO_BUFFER.Ptr[3+gOffset] -- phase_change // !=0 enable changing the clock phase. some slaves change phase between write/read
                    IOBuf[5] = 0; // IO_BUFFER.Ptr[4+gOffset] -- phase_change_byte // byte index where phase change should happen
                    IOBuf[6] = num_bytes; // IO_BUFFER.Ptr[5+gOffset] -- num_bytes
                    IOBuf[7] = 0; // IO_BUFFER.Ptr[6+gOffset] -- not used
                    // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
                    // SPI_mode0(IO_BUFFER.Ptr[3 + gOffset], IO_BUFFER.Ptr[4 + gOffset], IO_BUFFER.Ptr[5 + gOffset], IO_BUFFER.Ptr);
                //    break;
                //case 1:
                //    IOBuf[2] = 1; // IO_BUFFER.Ptr[1+gOffset] -- 1 for SPI_mode1: two-byte SPI transfer. read flag: enable changing the clock phase on first byte and changing phase between first and second byte
                //    IOBuf[3] = 0; // IO_BUFFER.Ptr[2+gOffset] -- read flag
                //    // IO_BUFFER.Ptr[3+gOffset] -- not used
                //    // IO_BUFFER.Ptr[4+gOffset] -- not used
                //    // IO_BUFFER.Ptr[5+gOffset] -- not used; num_bytes = 2
                //    // IO_BUFFER.Ptr[6+gOffset] -- not used
                //    // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
                //    // SPI_mode1(temp, IO_BUFFER.Ptr);
                //    break;
                //case 2:
                //    IOBuf[2] = 2; // IO_BUFFER.Ptr[1+gOffset] --2 for SPI_mode2: two-byte SPI transfer. read flag: enable changing the clock phase on first byte and sampling the LSb after the second byte read
                //    IOBuf[3] = 0; // IO_BUFFER.Ptr[2+gOffset] -- read flag
                //    // IO_BUFFER.Ptr[3+gOffset] -- not used
                //    // IO_BUFFER.Ptr[4+gOffset] -- not used
                //    // IO_BUFFER.Ptr[5+gOffset] -- not used; num_bytes = 2
                //    // IO_BUFFER.Ptr[6+gOffset] -- not used
                //    // IO_BUFFER.Ptr[7+gOffset] -- first byte of data starts on the (7 + offset) byte
                //    // SPI_mode2(temp, IO_BUFFER.Ptr);
                //    break;
            //}
            writeHID();
            // IO_BUFFER.Ptr[0] = SHORT_REPORT_ID;
            // IO_BUFFER.Ptr[0+gOffset] = 0; // error status					
            // IO_BUFFER.Ptr[1+gOffset] = gI2Cflags;
            // IO_BUFFER.Ptr[2+gOffset] = (SMB0CF & 0x10) >> 4; // EXTHOLD
            //gI2Cflags = IOBuf[2];
            //EXTHOLD = IOBuf[3];
            for (uint byteIndex = 0; byteIndex < byteCount; byteIndex++)
            {
                misoBuffer[byteIndex] = IOBuf[byteIndex + 2];
            }
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }

        /// <summary>
        /// <para>Firmware command code 23 OUT = enable INT0 (or Mock HID FIFO data diagnostic) </para>
        /// <para>
        /// - INT0Enable(0) disabled (EX0=0)
        /// - INT0Enable(1) real hardware (EX0=1)
        /// - INT0Enable(2) Mock HID x1 channel    101, 102, 103, ...
        /// - INT0Enable(3) Mock HID x2 channels   101, 201, 102, 202, 103, 203, ...
        /// - INT0Enable(4) Mock HID x3 channels   101, 201, 301, 102, 202, 302, 103, 203, 303, ...
        /// - INT0Enable(5) Mock HID x4 channels   101, 201, 301, 401, 102, 202, 302, 402, 103, 203, 303, 403, ...
        /// </para>
        /// </summary>
        /// <param name="EX0"></param>
        public void INT0Enable(byte EX0)
        {
            // https://jira.maxim-ic.com/browse/OS24EVK-24 Refactor: HID report enum and wrappers
            // reset report counter in the HID and enable INT0
            // verify: https://jira.maxim-ic.com/browse/OS24EVK-57 mutex lock HID.IOBuf[] in INT0Enable
            mutexGuardIOBuf.WaitOne();   // Wait until it is safe to enter.
            FlushQueue();
            Array.Clear(IOBuf, 0, IOBuf.Length);
            reportID();
            IOBuf[1] = 23; // enable INT0 -- see F3xx_USB0_ReportHandler.c void OUT_REPORT_HANDLER(int internalCall) 
            IOBuf[2] = EX0; // reportTypeFlag; // 1;
            writeHID();
            _firmwareINT0Enabled = EX0;
            mutexGuardIOBuf.ReleaseMutex();    // Release the Mutex.
        }
        private byte _firmwareINT0Enabled = 0;
        public byte FirmwareINT0Enabled { get { return _firmwareINT0Enabled; } }

        /// <summary>
        /// Search for a device attached to I2C bus, 
        /// given a list of poossible device addresses, 
        /// and a constant device ID register to test.
        /// </summary>
        /// <param name="I2C_DeviceAddressList_8bitLeftJustified">List of possible I2C device addresses to test. I2C device addresses are 8-bit left-justified (LSB=R/w bit)</param>
        /// <param name="DeviceId_RegAddress">device register address of a constant "Device ID" register</param>
        /// <param name="DeviceId_RegValue_Expect">register value of the constant "Device ID" register</param>
        /// <returns>I2C device addresses, or 0 if not found</returns>
        public byte SearchI2CdeviceAddressList(byte[] I2C_DeviceAddressList_8bitLeftJustified, byte DeviceId_RegAddress, byte DeviceId_RegValue_Expect)
        {
            // https://jira.maxim-ic.com/browse/OS24EVK-57 accelerometer support: SearchI2CdeviceAddressList optional I2C device
            foreach (byte test_I2C_Address in I2C_DeviceAddressList_8bitLeftJustified)
            {
                //try
                //{
                    byte[] data = new byte[2];
                    byte[] reg = new byte[1];
                    reg[0] = (byte)DeviceId_RegAddress;
                    // readI2C should already take care of mutex lock / unlock
                    // https://jira.maxim-ic.com/browse/OS24EVK-59 avoid NACK exception in SearchI2CdeviceAddressList
                    bool ignoreNACK = true;
                    int status = readI2C(test_I2C_Address, 2, 1, data, reg, ignoreNACK);
                    if (status == HID.I2C_NACK_ERROR)
                    {
                        continue;
                    }
                    else
                    {
                        byte DeviceId_RegValue_Actual = data[0];
                        if (DeviceId_RegValue_Actual == DeviceId_RegValue_Expect)
                        {
                            return test_I2C_Address;
                        }
                    }
                //}
                //catch (Exception)
                //{
                //    // myHID.readI2C can throw Exception("invalid I2C address");
                //}
            }
            return 0;
        }

    }
}