diff --git a/MultiTerm.Core/ViewModel/TerminalViewModel.cs b/MultiTerm.Core/ViewModel/TerminalViewModel.cs
index b4c8c9c..c3045ba 100644
--- a/MultiTerm.Core/ViewModel/TerminalViewModel.cs
+++ b/MultiTerm.Core/ViewModel/TerminalViewModel.cs
@@ -108,6 +108,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
// unsubscribe events
this.CommunicationProtocol.ReceivedDataEvent -= CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent -= CommunicationProtocol_SentDataEvent;
+ this.CommunicationProtocol.DisconnectedEvent -= CommunicationProtocol_DisconnectedEvent;
// remove reference to object
this.CommunicationProtocol = null;
}
@@ -125,6 +126,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
{
this.CommunicationProtocol.ReceivedDataEvent += CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent += CommunicationProtocol_SentDataEvent;
+ this.CommunicationProtocol.DisconnectedEvent += CommunicationProtocol_DisconnectedEvent;
}
// initializes default newline separators, updates them directly inside ReceivedData and SentData objects
@@ -151,6 +153,16 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
this.SentData.HandleNewData(e.SentData);
}
+ private void CommunicationProtocol_DisconnectedEvent(object? sender, DisconnectedEventArgs e)
+ {
+ // on unintentional disconnect the view model may still display connected => force to disconnected state
+ // allows user to change settings
+ if(e.Unintentional == true && this.ProtocolSettings!.DisplaysConnected == true)
+ {
+ this.ProtocolSettings.ForceConnectedState(false);
+ }
+ }
+
#endregion
///
diff --git a/MultiTerm.Protocols/CommunicationProtocol.cs b/MultiTerm.Protocols/CommunicationProtocol.cs
index 50a065f..f95a327 100644
--- a/MultiTerm.Protocols/CommunicationProtocol.cs
+++ b/MultiTerm.Protocols/CommunicationProtocol.cs
@@ -49,14 +49,18 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
///
protected void OnUnintentionallyDisconnected()
{
- // update state
- this.IsConnected = false;
+ // perform all steps in a task, to prevent deadlock when this method is called from a thread that is joined.
+ Task.Run(() =>
+ {
+ // update state
+ this.IsConnected = false;
- this.CancelThreads();
- this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol));
+ this.CancelThreads();
+ this.logger.LogError($"'{nameof(OnUnintentionallyDisconnected)}()' called.", nameof(CommunicationProtocol));
- // raise event indicating an unintentional disconnect
- this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true));
+ // raise event indicating an unintentional disconnect
+ this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true));
+ });
}
///
@@ -84,7 +88,7 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
if (success == false)
{
this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol));
- this.messenger.Send(new GenericUserInterfaceMessage("Failed to send message", MessageImportance.High));
+ this.messenger.Send(new GenericUserInterfaceMessage("Failed to send data, for more information please check logfile.", MessageImportance.High));
}
return success;
diff --git a/MultiTerm.Protocols/IProtocolSettingsViewModel.cs b/MultiTerm.Protocols/IProtocolSettingsViewModel.cs
index 8306773..51fcf9e 100644
--- a/MultiTerm.Protocols/IProtocolSettingsViewModel.cs
+++ b/MultiTerm.Protocols/IProtocolSettingsViewModel.cs
@@ -1,4 +1,5 @@
-using MultiTerm.Protocols.Types;
+using CommunityToolkit.Mvvm.Input;
+using MultiTerm.Protocols.Types;
namespace MultiTerm.Protocols;
@@ -9,6 +10,11 @@ public interface IProtocolSettingsViewModel
///
ProtocolType ProtocolType { get; }
+ ///
+ /// Indicates if the displays ifself as if the protocol is connected.
+ ///
+ bool DisplaysConnected { get; }
+
///
/// Indicates wether the settings can currently be edited.
///
@@ -24,4 +30,14 @@ public interface IProtocolSettingsViewModel
/// Event that is thrown when the user requested to disconnect from the device.
///
event EventHandler? DisconnectRequested;
+
+ ///
+ /// Change the state from connected to disconnected or from disconnected to connected.
+ /// Does not throw or .
+ ///
+ ///
+ /// if true changes the state from disconnected to connected.
+ /// if false changes the state from connected to disconnected.
+ ///
+ void ForceConnectedState(bool connected);
}
\ No newline at end of file
diff --git a/MultiTerm.Protocols/ProtocolSettingsViewModel.cs b/MultiTerm.Protocols/ProtocolSettingsViewModel.cs
index af23709..86093bb 100644
--- a/MultiTerm.Protocols/ProtocolSettingsViewModel.cs
+++ b/MultiTerm.Protocols/ProtocolSettingsViewModel.cs
@@ -19,6 +19,15 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
public event EventHandler? ConnectRequested;
public event EventHandler? DisconnectRequested;
+ public bool DisplaysConnected {
+ get
+ {
+ if (this.ConnectDisconnectButtonText == connectedStateButtonText) return true;
+ else if (this.ConnectDisconnectButtonText == disconnectedStateButtonText) return false;
+ else { throw new Exception($"'{nameof(DisplaysConnected)}' has not recognized button text, cannot identify state of connection."); }
+ }
+ }
+
public abstract ProtocolType ProtocolType { get; }
[ObservableProperty]
@@ -44,7 +53,7 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
await Task.Factory.StartNew(() =>
{
// if currently disconnected
- if (this.ConnectDisconnectButtonText == disconnectedStateButtonText)
+ if (this.DisplaysConnected == false)
{
// CONNECT
this.AreEditable = false;
@@ -64,20 +73,31 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
{
// rollback
this.AreEditable = true;
+
}
}
// if currently connected
- else if (this.ConnectDisconnectButtonText == connectedStateButtonText)
+ else
{
// DISCONNECT
this.DisconnectRequested?.Invoke(this, EventArgs.Empty);
this.ConnectDisconnectButtonText = disconnectedStateButtonText;
this.AreEditable = true;
}
- else
- {
- throw new Exception($"'{nameof(ConnectDisconnectAsync)}()' has not recognized button text, cannot identify state of connection.");
- }
});
}
+
+ public void ForceConnectedState(bool connected)
+ {
+ if (connected)
+ {
+ this.ConnectDisconnectButtonText = connectedStateButtonText;
+ this.AreEditable = false;
+ }
+ else
+ {
+ this.ConnectDisconnectButtonText = disconnectedStateButtonText;
+ this.AreEditable = true;
+ }
+ }
}
diff --git a/MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs b/MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs
index 99be545..3b1e970 100644
--- a/MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs
+++ b/MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs
@@ -77,27 +77,34 @@ public class UsbHidProtocol : CommunicationProtocol
{
while (ct.IsCancellationRequested == false)
{
+ // if usb hid device is null => something is wrong => break loop
+ if (this.usbHidDevice == null)
+ {
+ this.OnUnintentionallyDisconnected();
+ break;
+ }
+
ReadOnlySpan readData;
- if (this.usbHidDevice != null)
+ try
{
// read with timeout
readData = this.usbHidDevice.ReadTimeout(200, (int)ReadTimeoutMs);
- // any data received?
- if(readData.Length > 0)
- {
- foreach (var readByte in readData)
- {
- // report new byte with event
- this.OnReceivedData(new ExtendedByte((byte)readByte));
- }
- }
}
- // if usb hid device is null => something is wrong => break loop
- else
+ catch // on exception => break loop
{
this.OnUnintentionallyDisconnected();
break;
}
+
+ // any data received?
+ if (readData.Length > 0)
+ {
+ foreach (var readByte in readData)
+ {
+ // report new byte with event
+ this.OnReceivedData(new ExtendedByte((byte)readByte));
+ }
+ }
}
}
@@ -108,7 +115,7 @@ public class UsbHidProtocol : CommunicationProtocol
Debug.WriteLine($"{nameof(InternalSendBytes)}() of {nameof(UsbHidProtocol)} has {bytesToSend.Count} bytes to send.");
- while(bytesToSend.Count > 0)
+ while (bytesToSend.Count > 0)
{
// take from the list the amount of bytes to send. ToList creates a shallow copy.
//bytesToSend.CopyTo(0, nextBytes, 0, Math.Min(numBytesToTake, bytesToSend.Count));
@@ -120,7 +127,7 @@ public class UsbHidProtocol : CommunicationProtocol
// check number of bytes to send
int numBytesToSend = nextBytes.Count();
- if(numBytesToSend > MaxBytesPerReport || numBytesToSend > byte.MaxValue || numBytesToSend <= 0)
+ if (numBytesToSend > MaxBytesPerReport || numBytesToSend > byte.MaxValue || numBytesToSend <= 0)
{
throw new Exception($"'{nameof(InternalSendBytes)}()': Invalid of bytes to send ({nameof(numBytesToSend)})");
}
@@ -137,10 +144,19 @@ public class UsbHidProtocol : CommunicationProtocol
return false;
}
- this.usbHidDevice.Write(bytes);
+ try
+ {
+ this.usbHidDevice.Write(sendableBytes.ToArray());
+ }
+ catch(Exception ex)
+ {
+ this.logger.LogException(ex, $"Failed to Write Data to USB HID device " +
+ $"VID={this.usbHidSettings!.VendorId}, PID={this.usbHidSettings.ProductId}.", nameof(UsbHidProtocol));
+ return false;
+ }
// report sent bytes
- foreach (byte b in bytes)
+ foreach (byte b in sendableBytes)
{
this.OnSentData(new ExtendedByte(b));
}
@@ -148,7 +164,7 @@ public class UsbHidProtocol : CommunicationProtocol
return true;
}
-
+
public static IEnumerable GetDevices()
{
var libDevicesInfo = Hid.Enumerate();