//-----------------------------------------------------------------------
//
// Copyright © 2012 Nils Hammar. All rights reserved.
//
//-----------------------------------------------------------------------
/*
* Software to access vehicle information via the OBD-II connector.
*
* Copyright © 2012 Nils Hammar
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
* Alternative licensing is possible, see the licensing document.
*
* The above text may not be removed or modified.
*/
namespace CanApp.SystemEvents
{
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Windows.Forms;
using global::Microsoft.Win32;
using global::SharedObjects.GUI;
///
/// Class for monitoring changes of available serial ports.
///
/// This is a hidden form that's active to take care of the events.
///
///
public class SerialPortMonitor : Form
{
///
/// Callback interface for serial port change events.
///
private ISerialPortMonitorCallback iSerialPortMonitorCallback;
///
/// Object to synchronize around.
///
private object lockObject = new object();
///
/// Name of port that was connected.
///
private string portname = null;
///
/// Indicate that poll thread shall run or not.
///
private bool doRun = true;
///
/// Current data poll thread.
///
private Thread pollThread = null;
///
/// Initializes a new instance of the class.
///
/// Instance of callback interface implementation.
public SerialPortMonitor(ISerialPortMonitorCallback iSerialPortMonitorCallback)
{
this.iSerialPortMonitorCallback = iSerialPortMonitorCallback;
this.RegisterHidNotification();
}
///
/// Start the polling thread.
///
public void startThread()
{
this.pollThread = new Thread(new ThreadStart(this.pollThreadImplementation));
this.pollThread.Name = "Serial Port Monitor";
this.pollThread.Start();
}
///
/// Close the monitor.
///
public new void Close()
{
this.doRun = false;
if (this.pollThread != null)
{
this.pollThread.Abort();
this.pollThread.Interrupt();
this.pollThread.Join(4000);
this.pollThread = null;
}
base.Close();
}
///
/// Process window events.
///
/// Windows message containing event information.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed, intentional.")]
protected override void WndProc(ref Message m)
{
switch (m.Msg)
{
case Win32.WM_DEVICECHANGE:
this.OnDeviceChange(ref m);
break;
}
base.WndProc(ref m);
}
///
/// Get the COM port names for a device.
///
/// Device instance ID.
/// Unique ID for device.
/// List of COM ports for device.
private static List ComPortNames(string DeviceInstanceId, string DeviceUniqueID)
{
List comports = new List();
string[] deviceInstance = DeviceInstanceId.Split(new char[] { '&' });
if (deviceInstance.Length > 1)
{
matchSimpleUsbDevice(deviceInstance, DeviceUniqueID, comports);
matchCompositeUsbDevice(deviceInstance, comports);
}
return comports;
}
///
/// Handle a composite USB device.
///
/// Device instance data parts.
/// List to populate with com ports on the device.
private static void matchCompositeUsbDevice(string[] deviceInstance, List comports)
{
string pattern = string.Format("^{0}.{1}.MI_", deviceInstance[0], deviceInstance[1]);
Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
RegistryKey rk1 = Registry.LocalMachine;
RegistryKey rk2 = rk1.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum");
foreach (string s3 in rk2.GetSubKeyNames())
{
RegistryKey rk3 = rk2.OpenSubKey(s3);
foreach (string s in rk3.GetSubKeyNames())
{
if (regex.Match(s).Success)
{
RegistryKey rk4 = rk3.OpenSubKey(s);
foreach (string s2 in rk4.GetSubKeyNames())
{
RegistryKey rk5 = rk4.OpenSubKey(s2);
string parentId = (string)rk5.GetValue("ParentIdPrefix");
if (parentId == null)
{
RegistryKey rk6 = rk5.OpenSubKey("Device Parameters");
comports.Add((string)rk6.GetValue("PortName"));
}
}
}
}
}
}
///
/// Handle a simple USB device.
///
/// Device instance data parts.
/// Unique ID for device.
/// List to populate with com ports on the device.
private static void matchSimpleUsbDevice(string[] deviceInstance, string DeviceUniqueID, List comports)
{
string pattern = string.Format("^{0}.{1}.{2}", deviceInstance[0], deviceInstance[1], DeviceUniqueID);
Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
RegistryKey rk1 = Registry.LocalMachine;
RegistryKey rk2 = rk1.OpenSubKey("SYSTEM\\CurrentControlSet\\Enum");
foreach (string s3 in rk2.GetSubKeyNames())
{
RegistryKey rk3 = rk2.OpenSubKey(s3);
foreach (string s in rk3.GetSubKeyNames())
{
if (regex.Match(s).Success)
{
RegistryKey rk4 = rk3.OpenSubKey(s);
foreach (string s2 in rk4.GetSubKeyNames())
{
RegistryKey rk5 = rk4.OpenSubKey(s2);
string parentId = (string)rk5.GetValue("ParentIdPrefix");
if (parentId == null)
{
RegistryKey rk6 = rk5.OpenSubKey("Device Parameters");
comports.Add((string)rk6.GetValue("PortName"));
}
}
}
}
}
}
///
/// Get name of device.
///
/// Device interface broadcast struct.
/// Device instance ID.
/// Device unique ID.
/// String with name of device.
private static string GetDeviceName(DEV_BROADCAST_DEVICEINTERFACE dvi, out string DeviceInstanceId, out string DeviceUniqueID)
{
DeviceInstanceId = "N/A";
DeviceUniqueID = string.Empty;
string[] Parts = dvi.dbcc_name.Split('#');
if (Parts.Length >= 3)
{
string DevType = Parts[0].Substring(Parts[0].IndexOf(@"?\") + 2);
DeviceInstanceId = Parts[1];
DeviceUniqueID = Parts[2];
string RegPath = @"SYSTEM\CurrentControlSet\Enum\" + DevType + "\\" + DeviceInstanceId + "\\" + DeviceUniqueID;
RegistryKey key = Registry.LocalMachine.OpenSubKey(RegPath);
if (key != null)
{
object result = key.GetValue("FriendlyName");
if (result != null)
{
return "FriendlyName: " + result.ToString();
}
result = key.GetValue("DeviceDesc");
if (result != null)
{
return "DeviceDesc: " + result.ToString();
}
}
}
return string.Empty;
}
///
/// Thread for polling for updated data.
///
private void pollThreadImplementation()
{
while (this.doRun)
{
lock (this.lockObject)
{
Monitor.Wait(this.lockObject);
}
this.iSerialPortMonitorCallback.refreshSerialPortInfo(this.portname);
Thread.Sleep(100);
}
}
///
/// Called when a device change occurs.
///
/// Windows message containing event information.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed, intentional.")]
private void OnDeviceChange(ref Message msg)
{
int wParam = (int)msg.WParam;
string DeviceInstanceId;
string DeviceUniqueID;
if (wParam == Win32.DBT_DEVICEARRIVAL)
{
DEV_BROADCAST_DEVICEINTERFACE hdr = (DEV_BROADCAST_DEVICEINTERFACE)msg.GetLParam(typeof(DEV_BROADCAST_DEVICEINTERFACE));
GetDeviceName(hdr, out DeviceInstanceId, out DeviceUniqueID);
List cpl = ComPortNames(DeviceInstanceId, DeviceUniqueID);
lock (this.lockObject)
{
if (cpl.Count > 0)
{
this.portname = cpl[0];
}
Monitor.PulseAll(this.lockObject);
}
}
else
{
if (wParam == Win32.DBT_DEVICEREMOVECOMPLETE)
{
DEV_BROADCAST_DEVICEINTERFACE hdr = (DEV_BROADCAST_DEVICEINTERFACE)msg.GetLParam(typeof(DEV_BROADCAST_DEVICEINTERFACE));
GetDeviceName(hdr, out DeviceInstanceId, out DeviceUniqueID);
//// List cpl = ComPortNames(DeviceInstanceId, DeviceUniqueID);
lock (this.lockObject)
{
this.portname = null;
Monitor.PulseAll(this.lockObject);
}
}
}
}
///
/// Register as HID Notification receiver.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "Reviewed, intentional.")]
private void RegisterHidNotification()
{
Win32.DEV_BROADCAST_DEVICEINTERFACE dbi = new Win32.DEV_BROADCAST_DEVICEINTERFACE();
int size = Marshal.SizeOf(dbi);
dbi.dbcc_size = size;
dbi.dbcc_devicetype = Win32.DBT_DEVTYP_DEVICEINTERFACE;
dbi.dbcc_reserved = 0;
dbi.dbcc_classguid = Win32.GUID_DEVINTERFACE_USB_DEVICE;
dbi.dbcc_name = 0;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(dbi, buffer, true);
IntPtr r = Win32.RegisterDeviceNotification(Handle, buffer, Win32.DEVICE_NOTIFY_WINDOW_HANDLE);
if (r == IntPtr.Zero)
{
int err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
MessageBox.Show(
"Error: " + err.ToString() + "\r\n",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}
///
/// Initializes the component.
///
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SerialPortMonitor));
this.SuspendLayout();
/*
* SerialPortMonitor
*/
this.ClientSize = new System.Drawing.Size(292, 273);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "SerialPortMonitor";
this.ResumeLayout(false);
}
}
///
/// Generic broadcast header.
///
[StructLayout(LayoutKind.Sequential)]
internal struct DEV_BROADCAST_HDR
{
///
/// Data size.
///
public uint dbch_Size;
///
/// Device type.
///
public uint dbch_DeviceType;
///
/// Reserved data.
///
public uint dbch_Reserved;
}
///
/// User-friendly device data struct.
///
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
internal struct DEV_BROADCAST_DEVICEINTERFACE
{
///
/// Data size.
///
public int dbcc_size;
///
/// Device type.
///
public int dbcc_devicetype;
///
/// Reserved data.
///
public int dbcc_reserved;
///
/// Class GUID.
///
public Guid dbcc_classguid;
///
/// Device name data.
///
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)]
public string dbcc_name;
}
///
/// Class for Windows calls.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:FileMayOnlyContainASingleClass", Justification = "Reviewed.")]
internal class Win32
{
///
/// Message for change in device list.
///
public const int WM_DEVICECHANGE = 0x0219;
///
/// Event code for device added.
///
public const int DBT_DEVICEARRIVAL = 0x8000;
///
/// Event code for device removed.
///
public const int DBT_DEVICEREMOVECOMPLETE = 0x8004;
///
/// Window handle.
///
public const int DEVICE_NOTIFY_WINDOW_HANDLE = 0;
///
/// Service descriptor.
///
public const int DEVICE_NOTIFY_SERVICE_HANDLE = 1;
///
/// Device type interface.
///
public const int DBT_DEVTYP_DEVICEINTERFACE = 5;
///
/// GUID for general USB devices.
///
internal static readonly Guid GUID_DEVINTERFACE_USB_DEVICE = new Guid("A5DCBF10-6530-11D2-901F-00C04FB951ED");
///
/// Struct describing device interface.
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")]
[StructLayout(LayoutKind.Sequential)]
public class DEV_BROADCAST_DEVICEINTERFACE
{
///
/// Data size.
///
public int dbcc_size;
///
/// Device type.
///
public int dbcc_devicetype;
///
/// Reserved data.
///
public int dbcc_reserved;
///
/// Class GUID.
///
public Guid dbcc_classguid;
///
/// Device name data.
///
public short dbcc_name;
}
///
/// DLL call for registering notification receiver.
///
/// Recipient method.
/// Filter for notifications.
/// Flags for notifications.
/// 'null' on failure.
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, int Flags);
}
}