Improvements for async device management and exclusive access to device (#376)

This commit is contained in:
Frank Robijn 2024-09-27 15:46:26 +02:00 коммит произвёл GitHub
Родитель 833aeb3fb8
Коммит c5b0e01fde
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
15 изменённых файлов: 882 добавлений и 375 удалений

213
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,213 @@
# EditorConfig for Visual Studio 2022: https://learn.microsoft.com/en-us/visualstudio/ide/create-portable-custom-editor-options?view=vs-2022
# This is a top-most .editorconfig file
root = true
#=====================================================
#
# nanoFramework specific settings
#
#
#=====================================================
[*]
# Generic EditorConfig settings
end_of_line = crlf
charset = utf-8-bom
# Visual Studio spell checker
spelling_languages = en-us
spelling_checkable_types = strings,identifiers,comments
spelling_error_severity = information
spelling_exclusion_path = spelling_exclusion.dic
#=====================================================
#
# Settings copied from the .NET runtime
#
# https://github.com/dotnet/runtime
#
#=====================================================
# Default settings:
# A newline ending every file
# Use 4 spaces as indentation
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
# Generated code
[*{_AssemblyInfo.cs,.notsupported.cs,AsmOffsets.cs}]
generated_code = true
# C# files
[*.cs]
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_switch_labels = true
csharp_indent_labels = one_less_than_current
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async:suggestion
# avoid this. unless absolutely necessary
dotnet_style_qualification_for_field = false:suggestion
dotnet_style_qualification_for_property = false:suggestion
dotnet_style_qualification_for_method = false:suggestion
dotnet_style_qualification_for_event = false:suggestion
# Types: use keywords instead of BCL types, and permit var only when the type is clear
csharp_style_var_for_built_in_types = false:suggestion
csharp_style_var_when_type_is_apparent = false:none
csharp_style_var_elsewhere = false:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
dotnet_style_predefined_type_for_member_access = true:suggestion
# name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
dotnet_naming_style.static_prefix_style.required_prefix = s_
dotnet_naming_style.static_prefix_style.capitalization = camel_case
# internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# Code style defaults
csharp_using_directive_placement = outside_namespace:suggestion
dotnet_sort_system_directives_first = true
csharp_prefer_braces = true:silent
csharp_preserve_single_line_blocks = true:none
csharp_preserve_single_line_statements = false:none
csharp_prefer_static_local_function = true:suggestion
csharp_prefer_simple_using_statement = false:none
csharp_style_prefer_switch_expression = true:suggestion
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_collection_expression = when_types_exactly_match
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
csharp_prefer_simple_default_expression = true:suggestion
# Expression-bodied members
csharp_style_expression_bodied_methods = true:silent
csharp_style_expression_bodied_constructors = true:silent
csharp_style_expression_bodied_operators = true:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = true:silent
# Pattern matching
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
# Null checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Other features
csharp_style_prefer_index_operator = false:none
csharp_style_prefer_range_operator = false:none
csharp_style_pattern_local_over_anonymous_function = false:none
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = do_not_ignore
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# License header
file_header_template = Licensed to the .NET Foundation under one or more agreements.\nThe .NET Foundation licenses this file to you under the MIT license.
# C++ Files
[*.{cpp,h,in}]
curly_bracket_next_line = true
indent_brace_style = Allman
# Xml project files
[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,nativeproj,locproj}]
indent_size = 2
[*.{csproj,vbproj,proj,nativeproj,locproj}]
charset = utf-8
# Xml build files
[*.builds]
indent_size = 2
# Xml files
[*.{xml,stylecop,resx,ruleset}]
indent_size = 2
# Xml config files
[*.{props,targets,config,nuspec}]
indent_size = 2
# YAML config files
[*.{yml,yaml}]
indent_size = 2
# Shell scripts
[*.sh]
end_of_line = lf
[*.{cmd,bat}]
end_of_line = crlf

Просмотреть файл

@ -1,18 +1,20 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Collections.Generic;
using nanoFramework.Tools.Debugger.PortComposite;
using nanoFramework.Tools.Debugger.PortSerial;
using nanoFramework.Tools.Debugger.PortTcpIp;
using System.Collections.Generic;
namespace nanoFramework.Tools.Debugger
{
// write intellisense documentation for this class
public abstract partial class PortBase : PortMessageBase
{
protected PortBase()
{
NanoFrameworkDevices = NanoFrameworkDevices.Instance;
}
#region creating serial instances

Просмотреть файл

@ -1,4 +1,7 @@
using System;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.ObjectModel;
namespace nanoFramework.Tools.Debugger
@ -10,7 +13,13 @@ namespace nanoFramework.Tools.Debugger
public static NanoFrameworkDevices Instance
{
get { return _instance.Value; }
get
{
lock (_instance)
{
return _instance.Value;
}
}
}
private NanoFrameworkDevices() { }

Просмотреть файл

@ -0,0 +1,108 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Threading;
using nanoFramework.Tools.Debugger.PortTcpIp;
namespace nanoFramework.Tools.Debugger.NFDevice
{
/// <summary>
/// Code that wants to access a device should use this system-wide exclusive access while
/// communicating to a device to prevent that another nanoFramework tool also wants to
/// communicate with the device.
/// </summary>
public static class GlobalExclusiveDeviceAccess
{
#region Fields
/// <summary>
/// Base name for the system-wide mutex that controls access to a device connected to a COM port.
/// </summary>
private const string MutexBaseName = "276545121198496AADD346A60F14EF8D_";
#endregion
#region Methods
/// <summary>
/// Communicate with a serial device and ensure the code to be executed as exclusive access to the device.
/// </summary>
/// <param name="serialPort">The serial port the device is connected to.</param>
/// <param name="communication">Code to execute while having exclusive access to the device</param>
/// <param name="millisecondsTimeout">Maximum time in milliseconds to wait for exclusive access</param>
/// <param name="cancellationToken">Cancellation token that can be cancelled to stop/abort running the <paramref name="communication"/>.
/// This method does not stop/abort execution of <paramref name="communication"/> after it has been started.</param>
/// <returns>Indicates whether the <paramref name="communication"/> has been executed. Returns <c>false</c> if exclusive access
/// cannot be obtained within <paramref name="millisecondsTimeout"/>, or if <paramref name="cancellationToken"/> was cancelled
/// before the <paramref name="communication"/> has been started.</returns>
public static bool CommunicateWithDevice(string serialPort, Action communication, int millisecondsTimeout = Timeout.Infinite, CancellationToken? cancellationToken = null)
{
return DoCommunicateWithDevice(serialPort, communication, millisecondsTimeout, cancellationToken);
}
/// <summary>
/// Communicate with a device accessible via the network and ensure the code to be executed as exclusive access to the device.
/// </summary>
/// <param name="address">The network address the device is connected to.</param>
/// <param name="communication">Code to execute while having exclusive access to the device</param>
/// <param name="millisecondsTimeout">Maximum time in milliseconds to wait for exclusive access</param>
/// <param name="cancellationToken">Cancellation token that can be cancelled to stop/abort running the <paramref name="communication"/>.
/// This method does not stop/abort execution of <paramref name="communication"/> after it has been started.</param>
/// <returns>Indicates whether the <paramref name="communication"/> has been executed. Returns <c>false</c> if exclusive access
/// cannot be obtained within <paramref name="millisecondsTimeout"/>, or if <paramref name="cancellationToken"/> was cancelled
/// before the <paramref name="communication"/> has been started.</returns>
public static bool CommunicateWithDevice(NetworkDeviceInformation address, Action communication, int millisecondsTimeout = Timeout.Infinite, CancellationToken? cancellationToken = null)
{
return DoCommunicateWithDevice($"{address.Host}:{address.Port}", communication, millisecondsTimeout, cancellationToken);
}
#endregion
#region Implementation
private static bool DoCommunicateWithDevice(string connectionKey, Action communication, int millisecondsTimeout, CancellationToken? cancellationToken)
{
for (bool retry = true; retry;)
{
retry = false;
var waitHandles = new List<WaitHandle>();
var mutex = new Mutex(false, $"{MutexBaseName}{connectionKey}");
waitHandles.Add(mutex);
CancellationTokenSource timeOutToken = null;
if (millisecondsTimeout > 0 && millisecondsTimeout != Timeout.Infinite)
{
timeOutToken = new CancellationTokenSource(millisecondsTimeout);
waitHandles.Add(timeOutToken.Token.WaitHandle);
}
if (cancellationToken.HasValue)
{
waitHandles.Add(cancellationToken.Value.WaitHandle);
}
try
{
if (WaitHandle.WaitAny(waitHandles.ToArray()) == 0)
{
communication();
return true;
}
}
catch (AbandonedMutexException)
{
// While this process is waiting on a mutex, the process that owned the mutex has been terminated
// without properly releasing the mutex.
// Try again, if this is the only remaining process it will re-create the mutex and get exclusive access.
retry = true;
}
finally
{
mutex.ReleaseMutex();
timeOutToken?.Dispose();
}
}
return false;
}
#endregion
}
}

Просмотреть файл

@ -1,10 +1,9 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@ -20,8 +19,6 @@ namespace nanoFramework.Tools.Debugger.PortComposite
IEnumerable<PortBase> ports,
bool startDeviceWatchers = true)
{
NanoFrameworkDevices = NanoFrameworkDevices.Instance;
_ports.AddRange(ports);
SubscribeToPortEvents();
@ -52,21 +49,27 @@ namespace nanoFramework.Tools.Debugger.PortComposite
private void OnPortDeviceEnumerationCompleted(object sender, EventArgs e)
{
DeviceEnumerationCompleted?.Invoke(this, EventArgs.Empty);
IsDevicesEnumerationComplete = (from p in _ports
where p.IsDevicesEnumerationComplete
select p).Any();
if (IsDevicesEnumerationComplete)
{
DeviceEnumerationCompleted?.Invoke(this, EventArgs.Empty);
}
}
/// <inheritdoc/>
/// <exception cref="NotImplementedException">This API is not available in </exception>
public override void AddDevice(string deviceId)
/// <exception cref="NotImplementedException">This API is not available in PortCompositeDeviceManager.</exception>
public override NanoDeviceBase AddDevice(string deviceId)
{
_ports.ForEach(p =>
{
p.AddDevice(deviceId);
});
// None of the Port*Manager has a check whether deviceId matches the ID handled by the manager,
// so we don't know how to add a device here.
throw new NotImplementedException();
}
public override void StartDeviceWatchers()
{
IsDevicesEnumerationComplete = false;
_ports.ForEach(p => p.StartDeviceWatchers());
}
@ -77,6 +80,7 @@ namespace nanoFramework.Tools.Debugger.PortComposite
public override void ReScanDevices()
{
IsDevicesEnumerationComplete = false;
Task.Run(() =>
{
_ports.ForEach(p => p.ReScanDevices());

Просмотреть файл

@ -1,7 +1,5 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
@ -9,6 +7,12 @@ namespace nanoFramework.Tools.Debugger
{
public interface IPort
{
/// <summary>
/// Gets the Instance ID of the port that is unique among all ports
/// (regardless of the type of port).
/// </summary>
string InstanceId { get; }
int AvailableBytes { get; }
int SendBuffer(byte[] buffer);

Просмотреть файл

@ -1,7 +1,5 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
@ -52,13 +50,15 @@ namespace nanoFramework.Tools.Debugger
/// </summary>
public bool IsDevicesEnumerationComplete { get; internal set; } = false;
public NanoFrameworkDevices NanoFrameworkDevices { get; protected set; }
public NanoFrameworkDevices NanoFrameworkDevices { get; }
/// <summary>
/// Adds a new <see cref="PortSerial"/> device to list of NanoFrameworkDevices.
/// Adds a new device to list of NanoFrameworkDevices.
/// </summary>
/// <param name="deviceId">The serial port name where the device is connected.</param>
public abstract void AddDevice(string deviceId);
/// <param name="deviceId">The unique ID (based on the connection properties) of the device.</param>
/// <returns>The device with the unique ID that is added or (if it was already discovered before) retrieved
/// from the list of devices. Returns <see langword="null"/> if no device has been added.</returns>
public abstract NanoDeviceBase AddDevice(string deviceId);
/// <summary>
/// Starts the device watchers.

Просмотреть файл

@ -1,11 +1,17 @@
using Microsoft.Win32;
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using nanoFramework.Tools.Debugger.NFDevice;
namespace nanoFramework.Tools.Debugger.PortSerial
{
@ -43,6 +49,17 @@ namespace nanoFramework.Tools.Debugger.PortSerial
/// </summary>
public event EventDeviceRemoved Removed;
/// <summary>
/// Represents a delegate method that is used to handle the AllNewDevicesAdded event.
/// </summary>
/// <param name="sender">The object that raised the event.</param>
public delegate void EventAllNewDevicesAdded(object sender);
/// <summary>
/// Raised when all newly discovered devices have been added
/// </summary>
public event EventAllNewDevicesAdded AllNewDevicesAdded;
/// <summary>
/// Gets or sets the status of the device watcher.
/// </summary>
@ -60,13 +77,15 @@ namespace nanoFramework.Tools.Debugger.PortSerial
/// <summary>
/// Starts the device watcher.
/// </summary>
public void Start()
/// <param name="portsToExclude">The collection of serial ports to ignore when searching for devices.
/// Changes in the collection after the start of the device watcher are taken into account.</param>
public void Start(ICollection<string> portsToExclude = null)
{
if (!_started)
{
_threadWatch = new Thread(() =>
{
StartWatcher();
StartWatcher(portsToExclude ?? []);
})
{
IsBackground = true,
@ -77,21 +96,30 @@ namespace nanoFramework.Tools.Debugger.PortSerial
}
}
private void StartWatcher()
private void StartWatcher(ICollection<string> portsToExclude)
{
_ownerManager.OnLogMessageAvailable($"PortSerial device watcher started @ Thread {_threadWatch.ManagedThreadId} [ProcessID: {Process.GetCurrentProcess().Id}]");
_ports = new List<string>();
_ports = [];
_started = true;
int newPortsDetected = 0;
var newPortsDetectedLock = new object();
Status = DeviceWatcherStatus.Started;
while (_started)
{
try
{
var ports = GetPortNames();
var ports = new List<string>();
lock (portsToExclude)
{
ports.AddRange(from p in GetPortNames()
where !portsToExclude.Contains(p)
select p);
}
// check for ports that departed
List<string> portsToRemove = new();
@ -120,10 +148,36 @@ namespace nanoFramework.Tools.Debugger.PortSerial
if (!_ports.Contains(port))
{
_ports.Add(port);
Added?.Invoke(this, port);
if (Added is not null)
{
if (PortSerialManager.GetRegisteredDevice(port) is null)
{
lock (newPortsDetectedLock)
{
newPortsDetected++;
}
Task.Run(async () =>
{
// Force true async running
await Task.Yield();
GlobalExclusiveDeviceAccess.CommunicateWithDevice(
port,
() => Added.Invoke(this, port)
);
lock (newPortsDetectedLock)
{
if (--newPortsDetected == 0)
{
AllNewDevicesAdded?.Invoke(this);
}
}
});
}
}
}
}
Thread.Sleep(200);
}
#if DEBUG
@ -145,17 +199,16 @@ namespace nanoFramework.Tools.Debugger.PortSerial
/// <summary>
/// Gets the list of serial ports.
/// </summary>
/// <returns>The list of serial ports.</returns>
public List<string> GetPortNames()
/// <returns>The list of serial ports that may be connected to a nanoDevice.</returns>
public static List<string> GetPortNames()
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? GetPortNames_Linux()
: RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? GetPortNames_OSX()
: RuntimeInformation.IsOSPlatform(OSPlatform.Create("FREEBSD")) ? GetPortNames_FreeBSD()
: GetPortNames_Windows();
}
private List<string> GetPortNames_Linux()
private static List<string> GetPortNames_Linux()
{
List<string> ports = new List<string>();
@ -176,7 +229,7 @@ namespace nanoFramework.Tools.Debugger.PortSerial
return ports;
}
private List<string> GetPortNames_OSX()
private static List<string> GetPortNames_OSX()
{
List<string> ports = new List<string>();
@ -213,7 +266,7 @@ namespace nanoFramework.Tools.Debugger.PortSerial
return ports;
}
private List<string> GetPortNames_FreeBSD()
private static List<string> GetPortNames_FreeBSD()
{
List<string> ports = new List<string>();
@ -236,13 +289,49 @@ namespace nanoFramework.Tools.Debugger.PortSerial
return ports;
}
private List<string> GetPortNames_Windows()
private static List<string> GetPortNames_Windows()
{
const string FindFullPathPattern = @"\\\\\?\\([\w]*)#([\w&]*)#([\w&]*)";
const string RegExPattern = @"\\Device\\([a-zA-Z]*)(\d)";
List<string> portNames = new List<string>();
try
{
// discard known system and other rogue devices
bool IsSpecialPort(string deviceFullPath)
{
if (deviceFullPath is not null)
{
// make it upper case for comparison
string deviceFULLPATH = deviceFullPath.ToUpperInvariant();
if (
deviceFULLPATH.StartsWith(@"\\?\ACPI") ||
// reported in https://github.com/nanoframework/Home/issues/332
// COM ports from Broadcom 20702 Bluetooth adapter
deviceFULLPATH.Contains(@"VID_0A5C+PID_21E1") ||
// reported in https://nanoframework.slack.com/archives/C4MGGBH1P/p1531660736000055?thread_ts=1531659631.000021&cid=C4MGGBH1P
// COM ports from Broadcom 20702 Bluetooth adapter
deviceFULLPATH.Contains(@"VID&00010057_PID&0023") ||
// reported in Discord channel
deviceFULLPATH.Contains(@"VID&0001009E_PID&400A") ||
// this seems to cover virtual COM ports from Bluetooth devices
deviceFULLPATH.Contains("BTHENUM") ||
// this seems to cover virtual COM ports by ELTIMA
deviceFULLPATH.Contains("EVSERIAL")
)
{
// don't even bother with this one
return true;
}
}
return false;
}
// Gets the list of supposed open ports
RegistryKey allPorts = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM");
RegistryKey deviceFullPaths = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\COM Name Arbiter\Devices");
@ -271,7 +360,8 @@ namespace nanoFramework.Tools.Debugger.PortSerial
if (portKeyInfo != null)
{
string portName = (string)allPorts.GetValue(port);
if (portName != null)
if (portName != null
&& !IsSpecialPort((string)deviceFullPaths.GetValue(portName)))
{
portNames.Add(portName);
}
@ -281,29 +371,32 @@ namespace nanoFramework.Tools.Debugger.PortSerial
else
{
string portName = (string)allPorts.GetValue(port);
if (portName != null)
string deviceFullPath = (string)deviceFullPaths.GetValue(portName);
if (deviceFullPath != null)
{
// Get the full qualified name of the device
string deviceFullPath = (string)deviceFullPaths.GetValue(portName);
if (deviceFullPath != null)
if (IsSpecialPort(deviceFullPath))
{
var devicePathDetail = Regex.Match(deviceFullPath.Replace("+", "&"), FindFullPathPattern);
if ((devicePathDetail.Success) && (devicePathDetail.Groups.Count == 4))
{
string devicePath = deviceFullPath.Split('#')[1];
// don't even bother with this one
continue;
}
RegistryKey device = Registry.LocalMachine.OpenSubKey($"SYSTEM\\CurrentControlSet\\Enum\\{devicePathDetail.Groups[1]}\\{devicePath}\\{devicePathDetail.Groups[3]}");
if (device != null)
// Get the full qualified name of the device
var devicePathDetail = Regex.Match(deviceFullPath.Replace("+", "&"), FindFullPathPattern);
if ((devicePathDetail.Success) && (devicePathDetail.Groups.Count == 4))
{
string devicePath = deviceFullPath.Split('#')[1];
RegistryKey device = Registry.LocalMachine.OpenSubKey($"SYSTEM\\CurrentControlSet\\Enum\\{devicePathDetail.Groups[1]}\\{devicePath}\\{devicePathDetail.Groups[3]}");
if (device != null)
{
string service = (string)device.GetValue("Service");
if (service != null)
{
string service = (string)device.GetValue("Service");
if (service != null)
activePorts = Registry.LocalMachine.OpenSubKey($"SYSTEM\\CurrentControlSet\\Services\\{service}\\Enum");
if (activePorts != null)
{
activePorts = Registry.LocalMachine.OpenSubKey($"SYSTEM\\CurrentControlSet\\Services\\{service}\\Enum");
if (activePorts != null)
{
// If the device is still plugged, it should appear as valid here, if not present, it means, the device has been disconnected
portNames.Add(portName);
}
// If the device is still plugged, it should appear as valid here, if not present, it means, the device has been disconnected
portNames.Add(portName);
}
}
}

Просмотреть файл

@ -1,11 +1,6 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using Microsoft.Win32;
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -14,6 +9,8 @@ using System.IO.Ports;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
namespace nanoFramework.Tools.Debugger.PortSerial
{
@ -27,10 +24,6 @@ namespace nanoFramework.Tools.Debugger.PortSerial
// counter of device watchers completed
private int _deviceWatchersCompletedCount = 0;
// counter of device watchers completed
private int _newDevicesCount = 0;
private readonly object _newDevicesCountLock = new object();
private readonly Random _delay = new Random(DateTime.Now.Millisecond);
private readonly ConcurrentDictionary<string, CachedDeviceInfo> _devicesCache = new ConcurrentDictionary<string, CachedDeviceInfo>();
@ -40,9 +33,12 @@ namespace nanoFramework.Tools.Debugger.PortSerial
/// <summary>
/// Creates an Serial debug client
/// </summary>
/// <param name="startDeviceWatchers">Indicates whether to start the device watcher.</param>
/// <param name="portExclusionList">The collection of serial ports to ignore when searching for devices.
/// Changes in the collection after the start of the device watcher are taken into account.</param>
/// <param name="bootTime"></param>
public PortSerialManager(bool startDeviceWatchers = true, List<string> portExclusionList = null, int bootTime = 3000)
{
NanoFrameworkDevices = NanoFrameworkDevices.Instance;
_deviceWatcher = new(this);
BootTime = bootTime;
@ -66,8 +62,6 @@ namespace nanoFramework.Tools.Debugger.PortSerial
public override void ReScanDevices()
{
_newDevicesCount = 0;
// need to reset this here to have intimidate effect
IsDevicesEnumerationComplete = false;
@ -101,6 +95,7 @@ namespace nanoFramework.Tools.Debugger.PortSerial
{
_deviceWatcher.Added += OnDeviceAdded;
_deviceWatcher.Removed += OnDeviceRemoved;
_deviceWatcher.AllNewDevicesAdded += ProcessDeviceEnumerationComplete;
}
public void StartSerialDeviceWatchers()
@ -116,11 +111,10 @@ namespace nanoFramework.Tools.Debugger.PortSerial
{
// Start all device watchers
_deviceWatcher.Start();
_deviceWatcher.Start(PortExclusionList);
_watchersStarted = true;
_deviceWatchersCompletedCount = 0;
IsDevicesEnumerationComplete = false;
}
@ -146,8 +140,13 @@ namespace nanoFramework.Tools.Debugger.PortSerial
private void NanoFrameworkDevicesRemoveAllSerial()
{
List<string> devicesToRemove;
// also clear nanoFramework devices list
var devicesToRemove = NanoFrameworkDevices.Select(nanoDevice => ((NanoDevice<NanoSerialDevice>)nanoDevice).DeviceId).ToList();
lock (NanoFrameworkDevices)
{
devicesToRemove = NanoFrameworkDevices.Select(nanoDevice => ((NanoDevice<NanoSerialDevice>)nanoDevice).DeviceId).ToList();
}
foreach (var deviceId in devicesToRemove)
{
@ -158,140 +157,153 @@ namespace nanoFramework.Tools.Debugger.PortSerial
#endregion
#region Methods to manage device list add, remove, etc
/// <summary>
/// Get the device that communicates via the serial port, provided it has been added to the
/// list of known devices.
/// </summary>
/// <param name="portName">The port name of the device to get.</param>
/// <returns>The <see cref="NanoDeviceBase"/> that communicates via the serial port, or <see langword="null"/> if the device is not found.</returns>
public static NanoDeviceBase GetRegisteredDevice(string portName)
{
if (!string.IsNullOrWhiteSpace(portName))
{
var devices = NanoFrameworkDevices.Instance;
lock (devices)
{
return devices.FirstOrDefault(d => (d as NanoDevice<NanoSerialDevice>)?.DeviceId == portName);
}
}
return null;
}
/// <summary>
/// Adds a new <see cref="PortSerial"/> device to list of NanoFrameworkDevices.
/// </summary>
/// <param name="deviceId">The serial port name where the device is connected.</param>
public override void AddDevice(string deviceId)
/// <returns>The device with the unique ID that is added or (if it was already discovered before) retrieved
/// from the list of devices. Returns <see langword="null"/> if no device has been added.</returns>
public override NanoDeviceBase AddDevice(string deviceId)
{
AddDeviceToListAsync(deviceId);
return AddDeviceToListAsync(deviceId);
}
/// <summary>
/// Creates a <see cref="NanoDevice"/> and adds it to the list of devices.
/// Creates a <see cref="NanoDevice{NanoSerialDevice}"/> and adds it to the list of devices.
/// </summary>
/// <param name="deviceId">The AQS used to find this device</param>
private void AddDeviceToListAsync(string deviceId)
private NanoDeviceBase AddDeviceToListAsync(string deviceId)
{
// search the nanoFramework device list for a device with a matching interface ID
var nanoFrameworkDeviceMatch = FindNanoFrameworkDevice(deviceId);
// Add the device if it's new
if (nanoFrameworkDeviceMatch == null)
if (nanoFrameworkDeviceMatch is null)
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.CandidateDevice(deviceId));
if (nanoFrameworkDeviceMatch == null)
// Create a new element for this device and...
var newNanoFrameworkDevice = new NanoDevice<NanoSerialDevice>();
newNanoFrameworkDevice.DeviceId = deviceId;
newNanoFrameworkDevice.ConnectionPort = new PortSerial(this, newNanoFrameworkDevice);
newNanoFrameworkDevice.Transport = TransportType.Serial;
var connectResult = newNanoFrameworkDevice.ConnectionPort.ConnectDevice();
if (connectResult == ConnectPortResult.Unauthorized)
{
// Create a new element for this device and...
var newNanoFrameworkDevice = new NanoDevice<NanoSerialDevice>();
newNanoFrameworkDevice.DeviceId = deviceId;
newNanoFrameworkDevice.ConnectionPort = new PortSerial(this, newNanoFrameworkDevice);
newNanoFrameworkDevice.Transport = TransportType.Serial;
var connectResult = newNanoFrameworkDevice.ConnectionPort.ConnectDevice();
if (connectResult == ConnectPortResult.Unauthorized)
OnLogMessageAvailable(NanoDevicesEventSource.Log.UnauthorizedAccessToDevice(deviceId));
}
else if (connectResult == ConnectPortResult.Connected)
{
if (CheckValidNanoFrameworkSerialDevice(newNanoFrameworkDevice))
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.UnauthorizedAccessToDevice(deviceId));
//add device to the collection
NanoFrameworkDeviceAdd(newNanoFrameworkDevice);
OnLogMessageAvailable(NanoDevicesEventSource.Log.ValidDevice($"{newNanoFrameworkDevice.Description}"));
nanoFrameworkDeviceMatch = newNanoFrameworkDevice;
}
else if (connectResult == ConnectPortResult.Connected)
else
{
if (CheckValidNanoFrameworkSerialDevice(newNanoFrameworkDevice))
{
//add device to the collection
NanoFrameworkDeviceAdd(newNanoFrameworkDevice);
// disconnect
newNanoFrameworkDevice.Disconnect();
OnLogMessageAvailable(NanoDevicesEventSource.Log.ValidDevice($"{newNanoFrameworkDevice.Description}"));
// devices powered by the USB cable and that feature a serial converter (like an FTDI chip)
// are still booting when the USB enumeration event raises
// so need to give them enough time for the boot sequence to complete before trying to communicate with them
// Failing to connect to debugger engine on first attempt occurs frequently on dual USB devices like ESP32 WROVER KIT.
// Seems to be something related with both devices using the same USB endpoint
// Another reason is that an ESP32 takes around 3 seconds to complete the boot sequence and launch the CLR.
// Until then the device will look non responsive or invalid to the detection mechanism that we're using.
// A nice workaround for this seems to be adding an extra random wait so the comms are not simultaneous.
int delay;
lock (_delay)
{
delay = _delay.Next(200, 600);
}
else
Thread.Sleep(BootTime + delay);
OnLogMessageAvailable(NanoDevicesEventSource.Log.CheckingValidDevice($" {newNanoFrameworkDevice.DeviceId} *** 2nd attempt ***"));
connectResult = newNanoFrameworkDevice.ConnectionPort.ConnectDevice();
if (connectResult == ConnectPortResult.Unauthorized)
{
// disconnect
newNanoFrameworkDevice.Disconnect();
// devices powered by the USB cable and that feature a serial converter (like an FTDI chip)
// are still booting when the USB enumeration event raises
// so need to give them enough time for the boot sequence to complete before trying to communicate with them
// Failing to connect to debugger engine on first attempt occurs frequently on dual USB devices like ESP32 WROVER KIT.
// Seems to be something related with both devices using the same USB endpoint
// Another reason is that an ESP32 takes around 3 seconds to complete the boot sequence and launch the CLR.
// Until then the device will look non responsive or invalid to the detection mechanism that we're using.
// A nice workaround for this seems to be adding an extra random wait so the comms are not simultaneous.
int delay;
lock (_delay)
OnLogMessageAvailable(NanoDevicesEventSource.Log.UnauthorizedAccessToDevice(deviceId));
}
else if (connectResult == ConnectPortResult.Connected)
{
if (CheckValidNanoFrameworkSerialDevice(newNanoFrameworkDevice, true))
{
delay = _delay.Next(200, 600);
}
NanoFrameworkDeviceAdd(newNanoFrameworkDevice);
Thread.Sleep(BootTime + delay);
OnLogMessageAvailable(NanoDevicesEventSource.Log.CheckingValidDevice($" {newNanoFrameworkDevice.DeviceId} *** 2nd attempt ***"));
connectResult = newNanoFrameworkDevice.ConnectionPort.ConnectDevice();
if (connectResult == ConnectPortResult.Unauthorized)
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.UnauthorizedAccessToDevice(deviceId));
}
else if (connectResult == ConnectPortResult.Connected)
{
if (CheckValidNanoFrameworkSerialDevice(newNanoFrameworkDevice, true))
{
NanoFrameworkDeviceAdd(newNanoFrameworkDevice);
OnLogMessageAvailable(NanoDevicesEventSource.Log.ValidDevice($"{newNanoFrameworkDevice.Description}"));
}
else
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(deviceId));
}
OnLogMessageAvailable(NanoDevicesEventSource.Log.ValidDevice($"{newNanoFrameworkDevice.Description}"));
}
else
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(deviceId));
}
}
}
else
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(deviceId));
}
// subtract devices count
lock (_newDevicesCountLock)
{
_newDevicesCount--;
}
// check if we are done processing arriving devices
if (_newDevicesCount == 0)
{
ProcessDeviceEnumerationComplete();
else
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(deviceId));
}
}
}
else
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(deviceId));
}
}
return nanoFrameworkDeviceMatch;
}
/// <summary>
/// add device to the collection (if new)
/// Adds a device to the collection (if new).
/// </summary>
/// <param name="newNanoFrameworkDevice">new NanoSerialDevice</param>
/// <param name="newNanoFrameworkDevice">The new <see cref="NanoSerialDevice"/></param>
private void NanoFrameworkDeviceAdd(NanoDevice<NanoSerialDevice> newNanoFrameworkDevice)
{
if (newNanoFrameworkDevice != null && NanoFrameworkDevices.OfType<NanoDevice<NanoSerialDevice>>().Count(i => i.DeviceId == newNanoFrameworkDevice.DeviceId) == 0)
lock (NanoFrameworkDevices)
{
//add device to the collection
NanoFrameworkDevices.Add(newNanoFrameworkDevice);
if (newNanoFrameworkDevice != null && NanoFrameworkDevices.OfType<NanoDevice<NanoSerialDevice>>().Count(i => i.DeviceId == newNanoFrameworkDevice.DeviceId) == 0)
{
//add device to the collection
NanoFrameworkDevices.Add(newNanoFrameworkDevice);
}
}
}
public override void DisposeDevice(string instanceId)
{
var deviceToDispose = NanoFrameworkDevices.FirstOrDefault(nanoDevice => ((NanoDevice<NanoSerialDevice>)nanoDevice).DeviceId == instanceId);
NanoDeviceBase deviceToDispose;
lock (NanoFrameworkDevices)
{
deviceToDispose = NanoFrameworkDevices.FirstOrDefault(nanoDevice => ((NanoDevice<NanoSerialDevice>)nanoDevice).DeviceId == instanceId);
}
if (deviceToDispose != null)
{
@ -306,24 +318,46 @@ namespace nanoFramework.Tools.Debugger.PortSerial
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.DeviceDeparture(deviceId));
// get devices and remove them from collection
NanoFrameworkDevices.OfType<NanoDevice<NanoSerialDevice>>()
.Where(i => i.DeviceId == deviceId).ToList()
.ForEach(RemoveNanoFrameworkDevices);
List<NanoDevice<NanoSerialDevice>> devices;
lock (NanoFrameworkDevices)
{
// get devices
devices = NanoFrameworkDevices.OfType<NanoDevice<NanoSerialDevice>>()
.Where(i => i.DeviceId == deviceId).ToList();
}
// remove them from collection
devices.ForEach(RemoveNanoFrameworkDevices);
}
private void RemoveNanoFrameworkDevices(NanoDevice<NanoSerialDevice> device)
{
NanoFrameworkDevices.Remove(device);
device?.DebugEngine?.StopProcessing();
device?.DebugEngine?.Dispose();
if (device is null)
{
return;
}
lock (NanoFrameworkDevices)
{
NanoFrameworkDevices.Remove(device);
}
// get rid of debug engine, if that was created
device.DebugEngine?.StopProcessing();
device.DebugEngine?.Dispose();
// disconnect device in order to free port
device.Disconnect(true);
}
private NanoDeviceBase FindNanoFrameworkDevice(string deviceId)
{
if (deviceId != null)
{
return NanoFrameworkDevices.FirstOrDefault(d => (d as NanoDevice<NanoSerialDevice>).DeviceId == deviceId);
lock (NanoFrameworkDevices.Instance)
{
return NanoFrameworkDevices.FirstOrDefault(d => (d as NanoDevice<NanoSerialDevice>)?.DeviceId == deviceId);
}
}
return null;
@ -333,7 +367,7 @@ namespace nanoFramework.Tools.Debugger.PortSerial
/// Remove the device from the device list
/// </summary>
/// <param name="sender"></param>
/// <param name="deviceInformationUpdate"></param>
/// <param name="serialPort"></param>
private void OnDeviceRemoved(object sender, string serialPort)
{
RemoveDeviceFromList(serialPort);
@ -344,72 +378,27 @@ namespace nanoFramework.Tools.Debugger.PortSerial
/// This function will add the device to the listOfDevices
/// </summary>
/// <param name="sender"></param>
/// <param name="deviceInformation"></param>
/// <param name="serialPort"></param>
private void OnDeviceAdded(object sender, string serialPort)
{
// check against exclusion list
if (PortExclusionList.Contains(serialPort))
bool exclude;
lock (PortExclusionList)
{
exclude = PortExclusionList.Contains(serialPort);
}
if (exclude)
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.DroppingDeviceToExclude(serialPort));
return;
}
// discard known system and other rogue devices
RegistryKey portKeyInfo = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\COM Name Arbiter\Devices");
if (portKeyInfo != null)
{
var portInfo = (string)portKeyInfo.GetValue(serialPort);
if (portInfo != null)
{
Debug.WriteLine($"{nameof(OnDeviceAdded)}: port {serialPort}, portinfo: {portInfo}");
// make it upper case for comparison
portInfo = portInfo.ToUpperInvariant();
if (
portInfo.StartsWith(@"\\?\ACPI") ||
// reported in https://github.com/nanoframework/Home/issues/332
// COM ports from Broadcom 20702 Bluetooth adapter
portInfo.Contains(@"VID_0A5C+PID_21E1") ||
// reported in https://nanoframework.slack.com/archives/C4MGGBH1P/p1531660736000055?thread_ts=1531659631.000021&cid=C4MGGBH1P
// COM ports from Broadcom 20702 Bluetooth adapter
portInfo.Contains(@"VID&00010057_PID&0023") ||
// reported in Discord channel
portInfo.Contains(@"VID&0001009E_PID&400A") ||
// this seems to cover virtual COM ports from Bluetooth devices
portInfo.Contains("BTHENUM") ||
// this seems to cover virtual COM ports by ELTIMA
portInfo.Contains("EVSERIAL")
)
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.DroppingDeviceToExclude(serialPort));
// don't even bother with this one
return;
}
}
}
OnLogMessageAvailable(NanoDevicesEventSource.Log.DeviceArrival(serialPort));
lock (_newDevicesCountLock)
{
_newDevicesCount++;
}
Task.Run(() =>
{
Policy.Handle<InvalidOperationException>()
.WaitAndRetry(10, retryCount => TimeSpan.FromMilliseconds((retryCount * retryCount) * 25),
onRetry: (exception, delay, retryCount, context) => LogRetry(exception, delay, retryCount, context))
.Execute(() => AddDeviceToListAsync(serialPort));
});
Policy.Handle<InvalidOperationException>()
.WaitAndRetry(10, retryCount => TimeSpan.FromMilliseconds((retryCount * retryCount) * 25),
onRetry: (exception, delay, retryCount, context) => LogRetry(exception, delay, retryCount, context))
.Execute(() => AddDeviceToListAsync(serialPort));
}
private void LogRetry(Exception exception, TimeSpan delay, object retryCount, object context)
@ -425,12 +414,23 @@ namespace nanoFramework.Tools.Debugger.PortSerial
#region Handlers and events for Device Enumeration Complete
private void ProcessDeviceEnumerationComplete()
private void ProcessDeviceEnumerationComplete(object sender)
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.SerialDeviceEnumerationCompleted(NanoFrameworkDevices.Count));
int count;
lock (NanoFrameworkDevices)
{
if (IsDevicesEnumerationComplete)
{
// Nothing has changed
return;
}
// all watchers have completed enumeration
IsDevicesEnumerationComplete = true;
// all watchers have completed enumeration
IsDevicesEnumerationComplete = true;
count = NanoFrameworkDevices.OfType<NanoDevice<NanoSerialDevice>>().Count();
}
OnLogMessageAvailable(NanoDevicesEventSource.Log.SerialDeviceEnumerationCompleted(count));
// fire event that Serial enumeration is complete
OnDeviceEnumerationCompleted();

Просмотреть файл

@ -1,7 +1,5 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
@ -9,6 +7,8 @@ using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using nanoFramework.Tools.Debugger.NFDevice;
namespace nanoFramework.Tools.Debugger.PortTcpIp
{
@ -148,7 +148,21 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
switch (command)
{
case CommandDeviceStart:
Added?.Invoke(this, new NetworkDeviceInformation(host, port));
if (Added is not null)
{
var info = new NetworkDeviceInformation(host, port);
if (PortTcpIpManager.GetRegisteredDevice(info) is null)
{
Task.Run(async () =>
{
await Task.Yield(); // Force true async running
GlobalExclusiveDeviceAccess.CommunicateWithDevice(info, () =>
{
Added.Invoke(this, info);
});
});
}
}
break;
case CommandDeviceStop:

Просмотреть файл

@ -1,7 +1,5 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
@ -19,7 +17,7 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
private NanoNetworkDevice NanoNetworkDevice => NanoDevice.Device;
private string InstanceId => NanoDevice.DeviceId;
public string InstanceId => NanoDevice.DeviceId;
public override event EventHandler<StringEventArgs> LogMessageAvailable;
@ -140,4 +138,4 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
LogMessageAvailable?.Invoke(this, new StringEventArgs(message));
}
}
}
}

Просмотреть файл

@ -1,10 +1,6 @@
//
// Copyright (c) .NET Foundation and Contributors
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@ -12,6 +8,8 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
namespace nanoFramework.Tools.Debugger.PortTcpIp
{
@ -27,13 +25,14 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
// Network device watchers started flag
private bool _watchersStarted = false;
// counter of device watchers completed
private int _newDevicesCount = 0;
/// <summary>
/// Internal list with the actual nF Network devices
/// Internal list with the actual nF Network devices.
/// This must be a static list as NanoFrameworkDevices is also global.
/// Take care that all items of _networkDevices are in the NanoFrameworkDevices,
/// and that there are no devices in NanoFrameworkDevices that should be present
/// in _networkDevices but are not (and use NanoFrameworkDevices for locks).
/// </summary>
private readonly List<NetworkDeviceInformation> _networkDevices = new List<NetworkDeviceInformation>();
private static readonly List<NetworkDeviceInformation> _networkDevices = new List<NetworkDeviceInformation>();
private IEnumerable<NanoDevice<NanoNetworkDevice>> _networkNanoFrameworkDevices =>
NanoFrameworkDevices.Cast<NanoDevice<NanoNetworkDevice>>();
@ -48,8 +47,6 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
{
_deviceWatcher = new DeviceWatcher(this, discoveryPort);
NanoFrameworkDevices = NanoFrameworkDevices.Instance;
Task.Factory.StartNew(() =>
{
InitializeDeviceWatchers();
@ -126,21 +123,40 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
}
// Clear the list of devices so we don't have potentially disconnected devices around
ClearDeviceEntries();
// also clear nanoFramework devices list
var devicesToRemove = _networkNanoFrameworkDevices.Select(nanoDevice => nanoDevice.DeviceId).ToList();
List<string> devicesToRemove;
lock (NanoFrameworkDevices)
{
devicesToRemove = _networkNanoFrameworkDevices.Select(nanoDevice => nanoDevice.DeviceId).ToList();
}
foreach (var deviceId in devicesToRemove)
{
// get device...
var device = FindNanoFrameworkDevice(deviceId);
NanoDeviceBase device;
lock (NanoFrameworkDevices)
{
var deviceEntry = FindDevice(deviceId);
if (deviceEntry is null)
{
// this is not a TcpIp-connected device and is managed by another PortManager
continue;
}
// ... and remove it from collection
NanoFrameworkDevices.Remove(device);
// ... and remove it from collection
_networkDevices.Remove(deviceEntry);
device?.DebugEngine?.StopProcessing();
device?.DebugEngine?.Stop(true);
device = FindNanoFrameworkDevice(deviceId);
if (device is null)
{
continue;
}
// ... and remove it from collection
NanoFrameworkDevices.Remove(device);
}
device.DebugEngine?.StopProcessing();
device.DebugEngine?.Stop(true);
}
_watchersStarted = false;
@ -150,69 +166,91 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
#region Methods to manage device list add, remove, etc
/// <summary>
/// Get the device that communicates via the network port, provided it has been added to the
/// list of known devices.
/// </summary>
/// <param name="networkDevice">The name of the network device.</param>
/// <returns></returns>
public static NanoDeviceBase GetRegisteredDevice(NetworkDeviceInformation networkDevice)
{
if (networkDevice is not null)
{
var devices = NanoFrameworkDevices.Instance;
lock (devices)
{
return devices.FirstOrDefault(d => (d as NanoDevice<NanoNetworkDevice>)?.DeviceId == networkDevice.DeviceId);
}
}
return null;
}
/// <summary>
/// Creates a DeviceListEntry for a device and adds it to the list of devices
/// </summary>
private void AddDeviceToListAsync(NetworkDeviceInformation networkDevice)
private (NanoDeviceBase device, bool isNew) AddDeviceToListAsync(NetworkDeviceInformation networkDevice)
{
bool isNew = false;
// search the device list for a device with a matching interface ID
var networkMatch = FindDevice(networkDevice.DeviceId);
NetworkDeviceInformation networkMatch;
NanoDeviceBase nanoFrameworkDeviceMatch;
lock (NanoFrameworkDevices)
{
networkMatch = FindDevice(networkDevice.DeviceId);
// search the nanoFramework device list for a device with a matching interface ID
nanoFrameworkDeviceMatch = FindNanoFrameworkDevice(networkDevice.DeviceId);
}
// Add the device if it's new
if (networkMatch != null) return;
OnLogMessageAvailable(NanoDevicesEventSource.Log.CandidateDevice(networkDevice.DeviceId));
// search the nanoFramework device list for a device with a matching interface ID
var nanoFrameworkDeviceMatch = FindNanoFrameworkDevice(networkDevice.DeviceId);
if (nanoFrameworkDeviceMatch != null) return;
// Create a new element for this device and...
var newNanoFrameworkDevice = new NanoDevice<NanoNetworkDevice>();
newNanoFrameworkDevice.DeviceId = networkDevice.DeviceId;
newNanoFrameworkDevice.ConnectionPort = new PortTcpIp(this, newNanoFrameworkDevice, networkDevice);
newNanoFrameworkDevice.Transport = TransportType.TcpIp;
var connectResult = newNanoFrameworkDevice.ConnectionPort.ConnectDevice();
if (connectResult == ConnectPortResult.Unauthorized)
if (networkMatch is null && nanoFrameworkDeviceMatch is null)
{
OnLogMessageAvailable(
NanoDevicesEventSource.Log.UnauthorizedAccessToDevice(networkDevice.DeviceId));
}
else if (connectResult == ConnectPortResult.Connected)
{
if (CheckValidNanoFrameworkNetworkDevice(newNanoFrameworkDevice))
OnLogMessageAvailable(NanoDevicesEventSource.Log.CandidateDevice(networkDevice.DeviceId));
// Create a new element for this device and...
var newNanoFrameworkDevice = new NanoDevice<NanoNetworkDevice>();
newNanoFrameworkDevice.DeviceId = networkDevice.DeviceId;
newNanoFrameworkDevice.ConnectionPort = new PortTcpIp(this, newNanoFrameworkDevice, networkDevice);
newNanoFrameworkDevice.Transport = TransportType.TcpIp;
var connectResult = newNanoFrameworkDevice.ConnectionPort.ConnectDevice();
if (connectResult == ConnectPortResult.Unauthorized)
{
//add device to the collection
NanoFrameworkDevices.Add(newNanoFrameworkDevice);
_networkDevices.Add(networkDevice);
OnLogMessageAvailable(
NanoDevicesEventSource.Log.ValidDevice($"{newNanoFrameworkDevice.Description}"));
NanoDevicesEventSource.Log.UnauthorizedAccessToDevice(networkDevice.DeviceId));
}
else if (connectResult == ConnectPortResult.Connected)
{
if (CheckValidNanoFrameworkNetworkDevice(newNanoFrameworkDevice))
{
lock (NanoFrameworkDevices)
{
//add device to the collection
NanoFrameworkDevices.Add(newNanoFrameworkDevice);
_networkDevices.Add(networkDevice);
}
OnLogMessageAvailable(
NanoDevicesEventSource.Log.ValidDevice($"{newNanoFrameworkDevice.Description}"));
nanoFrameworkDeviceMatch = newNanoFrameworkDevice;
isNew = true;
}
else
{
// disconnect
newNanoFrameworkDevice.Disconnect();
}
}
else
{
// disconnect
newNanoFrameworkDevice.Disconnect();
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(networkDevice.DeviceId));
}
}
else
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.QuitDevice(networkDevice.DeviceId));
}
// subtract devices count
_newDevicesCount--;
// check if we are done processing arriving devices
if (_newDevicesCount == 0)
{
ProcessDeviceEnumerationComplete();
}
return (nanoFrameworkDeviceMatch, isNew);
}
public override void DisposeDevice(string instanceId)
@ -226,32 +264,39 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
private void RemoveDeviceFromList(NetworkDeviceInformation networkDevice)
{
// Removes the device entry from the internal list; therefore the UI
var deviceEntry = FindDevice(networkDevice.DeviceId);
OnLogMessageAvailable(NanoDevicesEventSource.Log.DeviceDeparture(networkDevice.DeviceId));
_networkDevices.Remove(deviceEntry);
// get device...
var device = FindNanoFrameworkDevice(networkDevice.DeviceId);
NanoDeviceBase device;
lock (NanoFrameworkDevices)
{
var deviceEntry = FindDevice(networkDevice.DeviceId);
if (deviceEntry != null)
{
_networkDevices.Remove(deviceEntry);
}
// ... and remove it from collection
NanoFrameworkDevices.Remove(device);
device = FindNanoFrameworkDevice(networkDevice.DeviceId);
if (device != null)
{
// ... and remove it from collection
NanoFrameworkDevices.Remove(device);
}
}
// get rid of debug engine, if that was created
device?.DebugEngine?.StopProcessing();
device?.DebugEngine?.Dispose();
}
private void ClearDeviceEntries()
{
_networkDevices.Clear();
// disconnect device
device?.Disconnect(true);
}
/// <summary>
/// Searches through the existing list of devices for the first DeviceListEntry that has
/// the specified device Id.
/// </summary>
internal NetworkDeviceInformation FindDevice(string deviceId) =>
private NetworkDeviceInformation FindDevice(string deviceId) =>
_networkDevices.FirstOrDefault(d => d.DeviceId == deviceId);
private NanoDeviceBase FindNanoFrameworkDevice(string deviceId) =>
@ -272,9 +317,12 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
{
OnLogMessageAvailable(NanoDevicesEventSource.Log.DeviceArrival(networkDevice.DeviceId));
_newDevicesCount++;
var (_, isNew) = AddDeviceToListAsync(networkDevice);
Task.Run(() => { AddDeviceToListAsync(networkDevice); });
if (isNew && !IsDevicesEnumerationComplete)
{
ProcessDeviceEnumerationComplete();
}
}
#endregion
@ -284,11 +332,16 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
private void ProcessDeviceEnumerationComplete()
{
OnLogMessageAvailable(
NanoDevicesEventSource.Log.SerialDeviceEnumerationCompleted(NanoFrameworkDevices.Count));
int count;
lock (NanoFrameworkDevices)
{
IsDevicesEnumerationComplete = true;
count = NanoFrameworkDevices.OfType<NanoDevice<NanoNetworkDevice>>().Count();
}
// all watchers have completed enumeration
IsDevicesEnumerationComplete = true;
// TODO: count are not serial devices
OnLogMessageAvailable(
NanoDevicesEventSource.Log.SerialDeviceEnumerationCompleted(count));
// fire event that Network enumeration is complete
OnDeviceEnumerationCompleted();
@ -494,7 +547,8 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
LogMessageAvailable?.Invoke(this, new StringEventArgs(message));
}
public override void AddDevice(string deviceId)
/// <inheritdoc/>
public override NanoDeviceBase AddDevice(string deviceId)
{
// expected format is "tcpip://{Host}:{Port}"
@ -504,9 +558,9 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
throw new ArgumentException("Invalid tcpip format.");
}
AddDeviceToListAsync(new NetworkDeviceInformation(
return AddDeviceToListAsync(new NetworkDeviceInformation(
match.Groups["host"].Value,
int.Parse(match.Groups["port"].Value)));
int.Parse(match.Groups["port"].Value))).device;
}
/// <summary>
@ -517,4 +571,4 @@ namespace nanoFramework.Tools.Debugger.PortTcpIp
#endregion
}
}
}

Просмотреть файл

@ -1,12 +1,6 @@
//
// Copyright (c) .NET Foundation and Contributors
// Portions Copyright (c) Microsoft Corporation. All rights reserved.
// See LICENSE file in the project root for full license information.
//
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using nanoFramework.Tools.Debugger.Extensions;
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
using System;
using System.Collections;
using System.Collections.Generic;
@ -16,6 +10,9 @@ using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using nanoFramework.Tools.Debugger.Extensions;
using nanoFramework.Tools.Debugger.WireProtocol;
using Polly;
namespace nanoFramework.Tools.Debugger
{
@ -77,6 +74,14 @@ namespace nanoFramework.Tools.Debugger
internal Engine(NanoDeviceBase device)
{
lock (_syncReqLockForPort)
{
if (!_syncReqLockForPort.TryGetValue(device.ConnectionPort.InstanceId, out _syncReqLock))
{
_syncReqLockForPort[device.ConnectionPort.InstanceId] = _syncReqLock = new SemaphoreSlim(1, 1);
}
}
InitializeLocal(device);
// default to false
@ -619,9 +624,10 @@ namespace nanoFramework.Tools.Debugger
/// <summary>
/// Global lock object for synchronizing message request. This ensures there is only one
/// outstanding request at any point of time.
/// outstanding request per device at any point of time.
/// </summary>
private static readonly SemaphoreSlim _syncReqLock = new SemaphoreSlim(1, 1);
private readonly static Dictionary<string, SemaphoreSlim> _syncReqLockForPort = [];
private readonly SemaphoreSlim _syncReqLock;
private IncomingMessage PerformSyncRequest(
uint command,
@ -821,42 +827,42 @@ namespace nanoFramework.Tools.Debugger
switch (bp.Cmd)
{
case Commands.c_Monitor_Ping:
{
// signal that a monitor ping was received
_pingEvent.Set();
Commands.Monitor_Ping.Reply cmdReply = new Commands.Monitor_Ping.Reply
{
// signal that a monitor ping was received
_pingEvent.Set();
Source = Commands.Monitor_Ping.c_Ping_Source_Host,
Flags = (StopDebuggerOnConnect ? Commands.Monitor_Ping.c_Ping_DbgFlag_Stop : 0)
};
Commands.Monitor_Ping.Reply cmdReply = new Commands.Monitor_Ping.Reply
{
Source = Commands.Monitor_Ping.c_Ping_Source_Host,
Flags = (StopDebuggerOnConnect ? Commands.Monitor_Ping.c_Ping_DbgFlag_Stop : 0)
};
PerformRequestAsync(
new OutgoingMessage(
_controlller.GetNextSequenceId(),
message,
_controlller.CreateConverter(),
Flags.c_NonCritical,
cmdReply)
);
PerformRequestAsync(
new OutgoingMessage(
_controlller.GetNextSequenceId(),
message,
_controlller.CreateConverter(),
Flags.c_NonCritical,
cmdReply)
);
return true;
}
return true;
}
case Commands.c_Monitor_Message:
{
Commands.Monitor_Message payload = message.Payload as Commands.Monitor_Message;
Debug.Assert(payload != null);
if (payload != null)
{
Commands.Monitor_Message payload = message.Payload as Commands.Monitor_Message;
Debug.Assert(payload != null);
if (payload != null)
{
Task.Factory.StartNew(() => _eventMessage?.Invoke(message, payload.ToString()));
}
return true;
Task.Factory.StartNew(() => _eventMessage?.Invoke(message, payload.ToString()));
}
return true;
}
case Commands.c_Debugging_Messaging_Query:
Debug.Assert(message.Payload != null);
Task.Factory.StartNew(() => RpcReceiveQuery(message, (Commands.Debugging_Messaging_Query)message.Payload));

Просмотреть файл

@ -67,6 +67,7 @@
<Compile Include="$(MSBuildThisFileDirectory)NetworkInformation\Wireless80211_ConfigurationOptions.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NetworkInformation\RadioType.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NFDevice\CachedDeviceInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NFDevice\GlobalExclusiveDeviceAccess.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NFDevice\NanoDeviceBase.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NFDevice\INanoDevice.cs" />
<Compile Include="$(MSBuildThisFileDirectory)NFDevice\NanoDevice.cs" />

1
spelling_exclusion.dic Normal file
Просмотреть файл

@ -0,0 +1 @@
nano