//----------------------------------------------------------------------- // // 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); } }