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. 18
      MultiTerm.Protocols/CommunicationProtocol.cs
  3. 18
      MultiTerm.Protocols/IProtocolSettingsViewModel.cs
  4. 32
      MultiTerm.Protocols/ProtocolSettingsViewModel.cs
  5. 50
      MultiTerm.Protocols/UsbHid/UsbHidProtocol.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
/// <summary>

@ -49,14 +49,18 @@ public abstract class CommunicationProtocol : ICommunicationProtocol
/// </summary>
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));
});
}
/// <summary>
@ -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<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;

@ -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
/// </summary>
ProtocolType ProtocolType { get; }
/// <summary>
/// Indicates if the <see cref="IProtocolSettingsViewModel"/> displays ifself as if the protocol is connected.
/// </summary>
bool DisplaysConnected { get; }
/// <summary>
/// Indicates wether the settings can currently be edited.
/// </summary>
@ -24,4 +30,14 @@ public interface IProtocolSettingsViewModel
/// Event that is thrown when the user requested to disconnect from the device.
/// </summary>
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? 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;
}
}
}

@ -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<byte> 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<UsbHidDeviceInfo> GetDevices()
{
var libDevicesInfo = Hid.Enumerate();

Loading…
Cancel
Save