mirror of https://github.com/duggerd/KFDtool.git
339 lines
12 KiB
C#
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;
|
|
}
|
|
}
|