using HidLibrary; using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace KFDtool.BSL430 { public class HIDusb : INotifyPropertyChanged { public const string NOTIFY_CONNECTION_STATE = "notifyConnectionState"; private Object ReadLock = new Object(); private Object WriteLock = new Object(); public bool IsConnected { get { if (_selectedDevice == null) return false; if (!_selectedDevice.IsOpen || !_selectedDevice.IsConnected) return false; return true; } } private int vid, pid; private HidFastReadDevice _selectedDevice = null; public delegate void ReadHandlerDelegate(HidReport report); public delegate void WriteHandlerDelegate(HidReport report); // List of packets to be sent through HID private List packetsOut = new List(); public List PacketsOut { get { lock (WriteLock) { return packetsOut; } } } private List packetsIn = new List(); public List PacketsIn { get { lock (ReadLock) { return packetsIn; } } } private bool sendingData = false; private int writeFailuresInARow = 0; // Worker handling the polling for USB device for connection private BackgroundWorker connectionWorker = new BackgroundWorker(); [DllImport("hid.dll", SetLastError = true)] protected static extern Boolean HidD_GetNumInputBuffers(int HidDeviceObject, ref int NumberBuffers); [DllImport("hid.dll", SetLastError = true)] protected static extern Boolean HidD_SetNumInputBuffers(IntPtr HidDeviceObject, Int32 NumberBuffers); public HIDusb(int vid, int pid) { this.vid = vid; this.pid = pid; // Need a polling worker to handle connection if device is not already connected connectionWorker.WorkerSupportsCancellation = true; connectionWorker.DoWork += connectionWorker_DoWork; autoReconnectToHidDevice(); // If not successfully connected, start the worker for polling every 500 ms if (_selectedDevice == null || !_selectedDevice.IsConnected) connectionWorker.RunWorkerAsync(); } public void Close() { // Cancel the connection worker if active connectionWorker.CancelAsync(); // De-register the events _selectedDevice.Inserted -= Device_Inserted; _selectedDevice.Removed -= Device_Removed; // Nullify the selected device _selectedDevice.CloseDevice(); _selectedDevice = null; } private void ResetConnection() { this.Close(); autoReconnectToHidDevice(); // If not successfully connected, start the worker for polling every 500 ms if (_selectedDevice == null || !_selectedDevice.IsConnected) connectionWorker.RunWorkerAsync(); } private void autoReconnectToHidDevice() { // No need to reconnect if device already connected if (_selectedDevice != null && _selectedDevice.IsConnected) return; // Try to connect to the device, if any _selectedDevice = HidDevices.EnumerateFastRead(vid, pid).FirstOrDefault(); if (_selectedDevice != null) { _selectedDevice.OpenDevice(); HidD_SetNumInputBuffers(_selectedDevice.ReadHandle, 512); _selectedDevice.Inserted += Device_Inserted; _selectedDevice.Removed += Device_Removed; _selectedDevice.MonitorDeviceEvents = true; } } // Detecting when a USB device is connected is very difficult in Windows and C# // If, when the constructor is called, the device is not detected, this worker // is to be called and run in the background an infinite loop until the device is // connected. This the worker ends. It is called only once, since the device is automatically // detected after a disconnection/reconnection of the USB cable. private void connectionWorker_DoWork(object sender, DoWorkEventArgs e) { while ((_selectedDevice == null || !_selectedDevice.IsConnected) && !connectionWorker.CancellationPending) { // Attempt connection again autoReconnectToHidDevice(); // Try again in 500 ms Thread.Sleep(500); } // Up to this point, the device was either connected or the thread was cancelled } /// /// Divides the buffer into chunks of 62 bytes and continuously send them. First byte in, first out. /// /// Data to be sent public void write(byte[] data) { if (data.Length == 0) return; lock (WriteLock) { // Count the number of packets to be sent int nbNewPackets = data.Length / 62; if (data.Length % 62 != 0) nbNewPackets++; // Create the packets for (int i = 0; i < nbNewPackets - 1 || (data.Length % 62 == 0 && i < nbNewPackets); i++) { byte[] buffer = new byte[64]; buffer[0] = 0x3f; // See Progammer's Guide: MSP430 USB API Stack for CDC/HID/MSC v2.0, page 39, section 7.3 buffer[1] = 62; for (int j = 0; j < 62; j++) buffer[j + 2] = data[j]; packetsOut.Add(buffer); } if (data.Length % 62 != 0) { int len = data.Length % 62; byte[] buffer = new byte[len + 2]; buffer[0] = 0x3f; // See Progammer's Guide: MSP430 USB API Stack for CDC/HID/MSC v2.0, page 39, section 7.3 buffer[1] = (byte)len; for (int j = 0; j < len; j++) buffer[j + 2] = data[j]; packetsOut.Add(buffer); } // If there is only 1 packet, then no data is being sent // Therefore, initiate the sending of the packet if (!sendingData) { sendingData = true; _selectedDevice.Write(packetsOut.First(), OnWriteReport); } } } /// /// Returns the first incomming packet in memory and remove it from the list. /// Thread safe unsing Lock. /// Returns null if no data left. /// /// public byte[] readNext() { lock (ReadLock) { if (packetsIn.Count == 0) return null; byte[] buf = packetsIn.First(); packetsIn.RemoveAt(0); return buf; } } // Fired when received data - or device disconnected private void OnReadReport(HidReport report) { lock (ReadLock) { // process your data here byte len = report.Data[0]; if (len > 0) { byte[] buf = new byte[len]; for (int i = 0; i < len; i++) buf[i] = report.Data[i + 1]; packetsIn.Add(buf); OnNewIncommingData(new EventArgs()); } // we need to start listening again for more data if (_selectedDevice != null && _selectedDevice.IsConnected) try { _selectedDevice.ReadReport(OnReadReport); } catch { } } } // Fired after data was sent to HID device private void OnWriteReport(bool success) { lock (WriteLock) { if (packetsOut.Count > 0) { // Remove the data afterwards, once it is properly sent if (success) { packetsOut.RemoveAt(0); writeFailuresInARow = 0; } else writeFailuresInARow++; // Sometimes, even if the device is connected and open, HIDLibrary fails to write reports. // To solve this, the device must be reset. This is a long call (500 ms). if (writeFailuresInARow > 3) { writeFailuresInARow = 0; this.ResetConnection(); Thread.Sleep(500); } // If more data to send, send it if (packetsOut.Count > 0) _selectedDevice.Write(packetsOut.First(), OnWriteReport); else sendingData = false; } } } private void Device_Inserted() { // Start reading - when incoming data, calls the callback _selectedDevice.ReadReport(OnReadReport); } private void Device_Removed() { // The device was removed. However, there is no need to reset _selectedDevice // as it will trigger Device_Inserted() as soon as it is inserted once again. sendingData = false; } // INTERNAL EVENTS private void OnConnected(EventArgs e) { EventHandler handler = Connected; if (handler != null) handler(this, e); } private void OnDisconnected(EventArgs e) { EventHandler handler = Disconnected; if (handler != null) handler(this, e); } private void OnNewIncommingData(EventArgs e) { EventHandler handler = NewIncommingData; if (handler != null) handler(this, e); } // Events to notify the subscribers (Main Application) that the Battery value changed private void OnPropertyChanged(PropertyChangedEventArgs e) { PropertyChangedEventHandler handler = PropertyChanged; if (handler != null) handler(this, e); } private void OnPropertyChanged(string propertyName) { OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); } /// /// Event for the App to register to in order to be notified when one of the /// accessible parameters is updated: battery value, received ACK, received message /// or connection status. /// public event PropertyChangedEventHandler PropertyChanged; public event EventHandler Connected; public event EventHandler Disconnected; public event EventHandler NewIncommingData; } }