#region CmdMessenger - MIT - (c) 2014 Thijs Elenbaas. /* CmdMessenger - library that provides command based messaging Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Copyright 2014 - Thijs Elenbaas */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Threading; namespace CommandMessenger.Transport.Serial { /// /// Class for storing last succesfull connection /// [Serializable] public class SerialConnectionManagerSettings { public String Port{ get; set; } public int BaudRate { get; set; } } /// /// Connection manager for serial port connection /// public class SerialConnectionManager : ConnectionManager { private enum ScanType { None, Quick, Thorough } private SerialConnectionManagerSettings _serialConnectionManagerSettings; private readonly ISerialConnectionStorer _serialConnectionStorer; private readonly SerialTransport _serialTransport; private readonly object _tryConnectionLock = new object(); private ScanType _scanType = ScanType.None; /// /// Available serial port names in system. /// public string[] AvailableSerialPorts { get; private set; } /// /// In scan mode allow to try different baud rates besides that is configured in SerialSettings. /// public bool DeviceScanBaudRateSelection { get; set; } /// /// Connection manager for serial port connection /// public SerialConnectionManager(SerialTransport serialTransport, CmdMessenger cmdMessenger, int watchdogCommandId = 0, string uniqueDeviceId = null, ISerialConnectionStorer serialConnectionStorer = null) : base(cmdMessenger, watchdogCommandId, uniqueDeviceId) { if (serialTransport == null) throw new ArgumentNullException("serialTransport", "Transport is null."); _serialTransport = serialTransport; _serialConnectionStorer = serialConnectionStorer; PersistentSettings = (_serialConnectionStorer != null); DeviceScanBaudRateSelection = true; UpdateAvailablePorts(); _serialConnectionManagerSettings = new SerialConnectionManagerSettings(); ReadSettings(); } /// /// Try connection /// /// Result private DeviceStatus TryConnection(string portName = null, int baudRate = int.MinValue) { lock (_tryConnectionLock) { // Save current port and baud rate string oldPortName = _serialTransport.CurrentSerialSettings.PortName; int oldBaudRate = _serialTransport.CurrentSerialSettings.BaudRate; // Update serial settings with new port and baud rate. if (portName != null) _serialTransport.CurrentSerialSettings.PortName = portName; if (baudRate != int.MinValue) _serialTransport.CurrentSerialSettings.BaudRate = baudRate; if (!_serialTransport.CurrentSerialSettings.IsValid()) { // Restore back previous settings if newly provided was invalid. _serialTransport.CurrentSerialSettings.PortName = oldPortName; _serialTransport.CurrentSerialSettings.BaudRate = oldBaudRate; return DeviceStatus.NotAvailable; } Connected = false; Log(1, @"Trying serial port " + _serialTransport.CurrentSerialSettings.PortName + " at " + _serialTransport.CurrentSerialSettings.BaudRate + " bauds."); if (_serialTransport.Connect()) { // Calculate optimal timeout for command. It should be not less than Serial Port timeout. Lets add additional 250ms. int optimalTimeout = _serialTransport.CurrentSerialSettings.Timeout + 250; DeviceStatus status = ArduinoAvailable(optimalTimeout); Connected = (status == DeviceStatus.Available); if (Connected) { Log(1, "Connected to serial port " + _serialTransport.CurrentSerialSettings.PortName + " at " + _serialTransport.CurrentSerialSettings.BaudRate + " bauds."); StoreSettings(); } else { _serialTransport.Disconnect(); } return status; } return DeviceStatus.NotAvailable; } } protected override void StartScan() { base.StartScan(); if (ConnectionManagerMode == Mode.Scan) { UpdateAvailablePorts(); _scanType = ScanType.None; } } //Try to connect using current connections settings and trigger event if succesful protected override void DoWorkConnect() { var activeConnection = false; try { activeConnection = TryConnection() == DeviceStatus.Available; } catch { // Do nothing } if (activeConnection) { ConnectionFoundEvent(); } } // Perform scan to find connected systems protected override void DoWorkScan() { // First try if currentConnection is open or can be opened var activeConnection = false; switch (_scanType) { case ScanType.None: try { activeConnection = TryConnection() == DeviceStatus.Available; } catch { // Do nothing } _scanType = ScanType.Quick; break; case ScanType.Quick: try { activeConnection = QuickScan(); } catch { // Do nothing } _scanType = ScanType.Thorough; break; case ScanType.Thorough: try { activeConnection = ThoroughScan(); } catch { // Do nothing } _scanType = ScanType.Quick; break; } // Trigger event when a connection was made if (activeConnection) { ConnectionFoundEvent(); } } private bool QuickScan() { Log(3, "Performing quick scan."); if (PersistentSettings) { // Then try if last stored connection can be opened Log(3, "Trying last stored connection."); if (TryConnection(_serialConnectionManagerSettings.Port, _serialConnectionManagerSettings.BaudRate) == DeviceStatus.Available) return true; } // Quickly run through most used baud rates var commonBaudRates = DeviceScanBaudRateSelection ? SerialUtils.CommonBaudRates : new [] { _serialTransport.CurrentSerialSettings.BaudRate }; foreach (var portName in AvailableSerialPorts) { // Get baud rates collection var baudRateCollection = DeviceScanBaudRateSelection ? SerialUtils.GetSupportedBaudRates(portName) : new[] { _serialTransport.CurrentSerialSettings.BaudRate }; var baudRates = commonBaudRates.Where(baudRateCollection.Contains).ToList(); if (baudRates.Any()) { Log(1, "Trying serial port " + portName + " using " + baudRateCollection.Length + " baud rate(s)."); // Now loop through baud rate collection foreach (var commonBaudRate in baudRates) { // Stop scanning if state was changed if (ConnectionManagerMode != Mode.Scan) return false; DeviceStatus status = TryConnection(portName, commonBaudRate); if (status == DeviceStatus.Available) return true; if (status == DeviceStatus.IdentityMismatch) break; // break the loop and continue to next port. } } // If port list has changed, interrupt scan and test new ports first if (NewPortScan()) return true; } if (!AvailableSerialPorts.Any()) { // Need to check for new ports if current ports list is empty if (NewPortScan()) return true; // Add small delay to reduce of Quick->Thorough->Quick->Thorough scan attempts - 400ms here + 100ms in main loop = ~500ms Thread.Sleep(400); } return false; } private bool ThoroughScan() { Log(1, "Performing thorough scan."); // Then try if last stored connection can be opened if (PersistentSettings && TryConnection(_serialConnectionManagerSettings.Port, _serialConnectionManagerSettings.BaudRate) == DeviceStatus.Available) return true; // Slowly walk through foreach (var portName in AvailableSerialPorts) { // Get baud rates collection var baudRateCollection = DeviceScanBaudRateSelection ? SerialUtils.GetSupportedBaudRates(portName) : new[] { _serialTransport.CurrentSerialSettings.BaudRate }; // Now loop through baud rate collection if (baudRateCollection.Any()) { Log(1, "Trying serial port " + portName + " using " + baudRateCollection.Length + " baud rate(s)."); foreach (var baudRate in baudRateCollection) { // Stop scanning if state was changed if (ConnectionManagerMode != Mode.Scan) return false; DeviceStatus status = TryConnection(portName, baudRate); if (status == DeviceStatus.Available) return true; if (status == DeviceStatus.IdentityMismatch) break; // break the loop and continue to next port. } } // If port list has changed, interrupt scan and test new ports first if (NewPortScan()) return true; } if (!AvailableSerialPorts.Any()) { // Need to check for new ports if current ports list is empty if (NewPortScan()) return true; // Add small delay to reduce of Quick->Thorough->Quick->Thorough scan attempts - 400ms here + 100ms in main loop = ~500ms Thread.Sleep(400); } return false; } private bool NewPortScan() { // Then see if port list has changed var newPorts = NewPortInList(); if (!newPorts.Any()) { return false; } //TODO: 4s - practical delay for Leonardo board, probably for other boards will be different. Need to investigate more on this. const int waitTime = 4000; Log(1, "New port(s) " + string.Join(",", newPorts) + " detected, wait for " + (waitTime / 1000.0) + "s before attempt to connect."); // Wait a bit before new port will be available then try to connect Thread.Sleep(waitTime); // Quickly run through most used ports var commonBaudRates = DeviceScanBaudRateSelection ? SerialUtils.CommonBaudRates : new[] { _serialTransport.CurrentSerialSettings.BaudRate }; foreach (var portName in newPorts) { // Get baud rates collection var baudRateCollection = DeviceScanBaudRateSelection ? SerialUtils.GetSupportedBaudRates(portName) : new[] { _serialTransport.CurrentSerialSettings.BaudRate }; // First add commonBaudRates available var sortedBaudRates = commonBaudRates.Where(baudRateCollection.Contains).ToList(); // Then add other BaudRates sortedBaudRates.AddRange(baudRateCollection.Where(baudRate => !commonBaudRates.Contains(baudRate))); foreach (var currentBaudRate in sortedBaudRates) { // Stop scanning if state was changed if (ConnectionManagerMode != Mode.Scan) return false; DeviceStatus status = TryConnection(portName, currentBaudRate); if (status == DeviceStatus.Available) return true; if (status == DeviceStatus.IdentityMismatch) break; // break the loop and continue to next port. } } return false; } private void UpdateAvailablePorts() { AvailableSerialPorts = SerialUtils.GetPortNames(); } private List NewPortInList() { var currentPorts = SerialUtils.GetPortNames(); var newPorts = currentPorts.Except(AvailableSerialPorts).ToList(); // Actualize ports collection AvailableSerialPorts = currentPorts; return newPorts; } protected override void StoreSettings() { if (!PersistentSettings) return; _serialConnectionManagerSettings.Port = _serialTransport.CurrentSerialSettings.PortName; _serialConnectionManagerSettings.BaudRate = _serialTransport.CurrentSerialSettings.BaudRate; _serialConnectionStorer.StoreSettings(_serialConnectionManagerSettings); } protected override sealed void ReadSettings() { if (!PersistentSettings) return; _serialConnectionManagerSettings = _serialConnectionStorer.RetrieveSettings(); } } }