KFDtool/sw/control/KFDtool.BSL430/HIDusb.cs

339 lines
12 KiB
C#

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<byte[]> packetsOut = new List<byte[]>();
public List<byte[]> PacketsOut
{
get
{
lock (WriteLock)
{
return packetsOut;
}
}
}
private List<byte[]> packetsIn = new List<byte[]>();
public List<byte[]> 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
}
/// <summary>
/// Divides the buffer into chunks of 62 bytes and continuously send them. First byte in, first out.
/// </summary>
/// <param name="data">Data to be sent</param>
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);
}
}
}
/// <summary>
/// Returns the first incomming packet in memory and remove it from the list.
/// Thread safe unsing Lock.
/// Returns null if no data left.
/// </summary>
/// <returns></returns>
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));
}
/// <summary>
/// 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.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler Connected;
public event EventHandler Disconnected;
public event EventHandler NewIncommingData;
}
}