implemented forcing state change of settingsVM when UnintentionalDisconnect happened,

made Sending and Receiving with USB HID more robust
master
Jonas Arnold 3 years ago
parent 2a3e37e7cc
commit 64fceb0fee
  1. 12
      MultiTerm.Core/ViewModel/TerminalViewModel.cs
  2. 6
      MultiTerm.Protocols/CommunicationProtocol.cs
  3. 18
      MultiTerm.Protocols/IProtocolSettingsViewModel.cs
  4. 28
      MultiTerm.Protocols/ProtocolSettingsViewModel.cs
  5. 42
      MultiTerm.Protocols/UsbHid/UsbHidProtocol.cs

@ -108,6 +108,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
// unsubscribe events // unsubscribe events
this.CommunicationProtocol.ReceivedDataEvent -= CommunicationProtocol_ReceivedDataEvent; this.CommunicationProtocol.ReceivedDataEvent -= CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent -= CommunicationProtocol_SentDataEvent; this.CommunicationProtocol.SentDataEvent -= CommunicationProtocol_SentDataEvent;
this.CommunicationProtocol.DisconnectedEvent -= CommunicationProtocol_DisconnectedEvent;
// remove reference to object // remove reference to object
this.CommunicationProtocol = null; this.CommunicationProtocol = null;
} }
@ -125,6 +126,7 @@ public abstract partial class TerminalViewModel : ObservableObject, ITerminalVie
{ {
this.CommunicationProtocol.ReceivedDataEvent += CommunicationProtocol_ReceivedDataEvent; this.CommunicationProtocol.ReceivedDataEvent += CommunicationProtocol_ReceivedDataEvent;
this.CommunicationProtocol.SentDataEvent += CommunicationProtocol_SentDataEvent; this.CommunicationProtocol.SentDataEvent += CommunicationProtocol_SentDataEvent;
this.CommunicationProtocol.DisconnectedEvent += CommunicationProtocol_DisconnectedEvent;
} }
// initializes default newline separators, updates them directly inside ReceivedData and SentData objects // 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); 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 #endregion
/// <summary> /// <summary>

@ -48,6 +48,9 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
/// To be called whenever the protocol detected that it is disconnected, but this is not a known fact. /// To be called whenever the protocol detected that it is disconnected, but this is not a known fact.
/// </summary> /// </summary>
protected void OnUnintentionallyDisconnected() protected void OnUnintentionallyDisconnected()
{
// perform all steps in a task, to prevent deadlock when this method is called from a thread that is joined.
Task.Run(() =>
{ {
// update state // update state
this.IsConnected = false; this.IsConnected = false;
@ -57,6 +60,7 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
// raise event indicating an unintentional disconnect // raise event indicating an unintentional disconnect
this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true)); this.DisconnectedEvent?.Invoke(this, new DisconnectedEventArgs(true));
});
} }
/// <summary> /// <summary>
@ -84,7 +88,7 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
if (success == false) if (success == false)
{ {
this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol)); this.logger.LogError($"'{nameof(SendBytes)}()' failed to send during {nameof(InternalSendBytes)}.", nameof(CommunicationProtocol));
this.messenger.Send<IUserInterfaceMessage>(new GenericUserInterfaceMessage("Failed to send message", MessageImportance.High)); this.messenger.Send<IUserInterfaceMessage>(new GenericUserInterfaceMessage("Failed to send data, for more information please check logfile.", MessageImportance.High));
} }
return success; return success;

@ -1,4 +1,5 @@
using MultiTerm.Protocols.Types; using CommunityToolkit.Mvvm.Input;
using MultiTerm.Protocols.Types;
namespace MultiTerm.Protocols; namespace MultiTerm.Protocols;
@ -9,6 +10,11 @@ public interface IProtocolSettingsViewModel
/// </summary> /// </summary>
ProtocolType ProtocolType { get; } ProtocolType ProtocolType { get; }
/// <summary>
/// Indicates if the <see cref="IProtocolSettingsViewModel"/> displays ifself as if the protocol is connected.
/// </summary>
bool DisplaysConnected { get; }
/// <summary> /// <summary>
/// Indicates wether the settings can currently be edited. /// Indicates wether the settings can currently be edited.
/// </summary> /// </summary>
@ -24,4 +30,14 @@ public interface IProtocolSettingsViewModel
/// Event that is thrown when the user requested to disconnect from the device. /// Event that is thrown when the user requested to disconnect from the device.
/// </summary> /// </summary>
event EventHandler? DisconnectRequested; event EventHandler? DisconnectRequested;
/// <summary>
/// Change the state from connected to disconnected or from disconnected to connected.
/// Does not throw <see cref="ConnectRequested"/> or <see cref="DisconnectRequested"/>.
/// </summary>
/// <param name="connected">
/// if true changes the state from disconnected to connected.
/// if false changes the state from connected to disconnected.
/// </param>
void ForceConnectedState(bool connected);
} }

@ -19,6 +19,15 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
public event EventHandler<ConnectionRequestEventArgs>? ConnectRequested; public event EventHandler<ConnectionRequestEventArgs>? ConnectRequested;
public event EventHandler? DisconnectRequested; 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; } public abstract ProtocolType ProtocolType { get; }
[ObservableProperty] [ObservableProperty]
@ -44,7 +53,7 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
await Task.Factory.StartNew(() => await Task.Factory.StartNew(() =>
{ {
// if currently disconnected // if currently disconnected
if (this.ConnectDisconnectButtonText == disconnectedStateButtonText) if (this.DisplaysConnected == false)
{ {
// CONNECT // CONNECT
this.AreEditable = false; this.AreEditable = false;
@ -64,20 +73,31 @@ public abstract partial class ProtocolSettingsViewModel : ObservableObject, IPro
{ {
// rollback // rollback
this.AreEditable = true; this.AreEditable = true;
} }
} }
// if currently connected // if currently connected
else if (this.ConnectDisconnectButtonText == connectedStateButtonText) else
{ {
// DISCONNECT // DISCONNECT
this.DisconnectRequested?.Invoke(this, EventArgs.Empty); this.DisconnectRequested?.Invoke(this, EventArgs.Empty);
this.ConnectDisconnectButtonText = disconnectedStateButtonText; this.ConnectDisconnectButtonText = disconnectedStateButtonText;
this.AreEditable = true; this.AreEditable = true;
} }
});
}
public void ForceConnectedState(bool connected)
{
if (connected)
{
this.ConnectDisconnectButtonText = connectedStateButtonText;
this.AreEditable = false;
}
else else
{ {
throw new Exception($"'{nameof(ConnectDisconnectAsync)}()' has not recognized button text, cannot identify state of connection."); this.ConnectDisconnectButtonText = disconnectedStateButtonText;
this.AreEditable = true;
} }
});
} }
} }

@ -77,13 +77,27 @@ public class UsbHidProtocol : CommunicationProtocol
{ {
while (ct.IsCancellationRequested == false) while (ct.IsCancellationRequested == false)
{ {
// if usb hid device is null => something is wrong => break loop
if (this.usbHidDevice == null)
{
this.OnUnintentionallyDisconnected();
break;
}
ReadOnlySpan<byte> readData; ReadOnlySpan<byte> readData;
if (this.usbHidDevice != null) try
{ {
// read with timeout // read with timeout
readData = this.usbHidDevice.ReadTimeout(200, (int)ReadTimeoutMs); readData = this.usbHidDevice.ReadTimeout(200, (int)ReadTimeoutMs);
}
catch // on exception => break loop
{
this.OnUnintentionallyDisconnected();
break;
}
// any data received? // any data received?
if(readData.Length > 0) if (readData.Length > 0)
{ {
foreach (var readByte in readData) foreach (var readByte in readData)
{ {
@ -92,13 +106,6 @@ public class UsbHidProtocol : CommunicationProtocol
} }
} }
} }
// if usb hid device is null => something is wrong => break loop
else
{
this.OnUnintentionallyDisconnected();
break;
}
}
} }
protected override bool InternalSendBytes(byte[] bytes) protected override bool InternalSendBytes(byte[] bytes)
@ -108,7 +115,7 @@ public class UsbHidProtocol : CommunicationProtocol
Debug.WriteLine($"{nameof(InternalSendBytes)}() of {nameof(UsbHidProtocol)} has {bytesToSend.Count} bytes to send."); 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. // 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)); //bytesToSend.CopyTo(0, nextBytes, 0, Math.Min(numBytesToTake, bytesToSend.Count));
@ -120,7 +127,7 @@ public class UsbHidProtocol : CommunicationProtocol
// check number of bytes to send // check number of bytes to send
int numBytesToSend = nextBytes.Count(); 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)})"); throw new Exception($"'{nameof(InternalSendBytes)}()': Invalid of bytes to send ({nameof(numBytesToSend)})");
} }
@ -137,10 +144,19 @@ public class UsbHidProtocol : CommunicationProtocol
return false; 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 // report sent bytes
foreach (byte b in bytes) foreach (byte b in sendableBytes)
{ {
this.OnSentData(new ExtendedByte(b)); this.OnSentData(new ExtendedByte(b));
} }

Loading…
Cancel
Save