SerialPort - wyświetlanie informacji z wag po porcie COM

0

Witam.
Potrzebuje nakierowania lekkiego, bo już 3 dni z tym walczę. Miałem wątek na forum o tym, że chciałem słuchać wag w .NET Core ale na szczęście ten pomysł przepadł. Wydawało mi się, że WinForms sobie z tym poradzi zdecydowanie lepiej, ale jednak nie.

Do systemu dopisuje sobie wagi. U tego klienta są dwie - pierwsza na COM2, druga na COM3. Czego potrzebuje:

  1. Dwa miejsca ważenia -> 1. Szybkie ważenie - pokazuje wagę, 2. Wydawanie - ważenie podczas kompletowania towaru (wydawania)
  2. Każde z tych miejsc ma możliwość przełączania wag. Do tego jest guzik "Zmień wagę"

Gdzie zmiana wagi nie robi dużego problemu w obrębie jednej funkcji, tak przełączenie się na pomiędzy Szybkim ważeniem, a Wydawaniem generuje kompletny chaos.

SYSTEM
Jedno główne okno MainForm, który zawiera panel i ten panel przyjmuje odpowiednie usercontrol - ucPacking lub ucWeighting.

Stworzyłem statyczną klasę, w której trzymam obiekt SerialPort

public static class SerialPortService
{
    public static SafeSerialPort SerialPort;
    private static Logger _logger;

    public static void CreateConnection(Scale s, Logger logger)
    {
        _logger = logger;
        if(SerialPort != null && SerialPort.IsOpen)
        {
            SerialPort.DiscardInBuffer();
            SerialPort.DiscardOutBuffer();
            SerialPort.Close();
        }

        SerialPort = new SafeSerialPort(s);
        SerialPort.Handshake = Handshake.None;
    }

    public static void ChangeScale(Scale s)
    {
        if (SerialPort != null && SerialPort.IsOpen)
        {
            if(SerialPort.IsOpen)
            {
                SerialPort.DiscardInBuffer();
                SerialPort.DiscardOutBuffer();
                SerialPort.Close();

                SerialPort = new SafeSerialPort(s);
                SerialPort.Handshake = Handshake.None;
            }
            else
            {
                SerialPort = new SafeSerialPort(s);
                SerialPort.Handshake = Handshake.None;
            }
        }
    }
}

Stworzyłem również klasę poprawiającą nieco Close(), ponieważ, .NET czegoś tam nie zwalnia domyślnie.

public class SafeSerialPort : SerialPort
{
    private Stream theBaseStream;
    private Scale _scale;

    public SafeSerialPort(Scale scale)
: base(scale.PortName, scale.BaundRate, scale.Parity, scale.DataBits, scale.StopBits)
    {
        _scale = scale;
    }

    public new void Open()
    {
        try
        {
            base.Open();
            theBaseStream = BaseStream;
            GC.SuppressFinalize(theBaseStream);
        }
        catch
        {

        }
    }

    public new void Dispose()
    {
        Dispose(true);
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && (base.Container != null))
        {
            base.Container.Dispose();
        }
        try
        {
            if (theBaseStream.CanRead)
            {
                theBaseStream.Close();
                GC.ReRegisterForFinalize(theBaseStream);
            }
        }
        catch
        {
            // ignore exception - bug with USB - serial adapters.
        }
        base.Dispose(disposing);
    }
}

Obie funkcje systemu mają taką samą implementacje połączenia i pobierania danych z wagi, więc wrzucę tylko raz

private Scale _selectedScale;
private object locker = new object();
private delegate void SerialPortDelegate(string data);
private SerialPortDelegate _delegate;

private void ucWeighting_Load(object sender, EventArgs e)
{
    _delegate = new SerialPortDelegate(ReadSerialPort);
    if (_settings.Scales.Count > 0)
    {
        _selectedScale = _settings.Scales[0];
        lbTitleScale.Text = "PROSTE WAŻENIE [" + _selectedScale.Name + "]";

        SerialPortService.CreateConnection(_selectedScale, _logger);
        SerialPortService.SerialPort.Open();
        SerialPortService.SerialPort.DataReceived += _serialPort_DataReceived;
    }
}

private async void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    await Task.Delay(500);
    if (!tbWeight.IsDisposed)
    {
        if (tbWeight.InvokeRequired)
        {
            string data = SerialPortService.SerialPort.ReadLine();
            tbWeight.Invoke(_delegate, new object[] { data });
        }
    }
}

private void btnChangeScale_Click(object sender, EventArgs e)
{
    try
    {
        _tara = new List<decimal>();
        int index = _settings.Scales.IndexOf(_selectedScale) + 1;
        if (index <= _settings.Scales.Count - 1)
        {
            _selectedScale = _settings.Scales[index];
        }
        else
        {
            _selectedScale = _settings.Scales[0];
        }

        lbTitleScale.Text = "PROSTE WAŻENIE [" + _selectedScale.Name + "]";

        SerialPortService.ChangeScale(_selectedScale);
        SerialPortService.SerialPort.Open();
        SerialPortService.SerialPort.DataReceived += _serialPort_DataReceived;
    }
    catch (Exception ex)
    {
        _scaleError = ex.Message;
        _logger.Error(ex, "[ucWeighting] WAGA");
        tbWeight.Text = "ERROR";
    }
}

BŁĘDY

Informacje o wyjątku: System.ArgumentOutOfRangeException
w System.Text.DecoderNLS.GetCharCount(Byte[], Int32, Int32, Boolean)
w System.Text.DecoderNLS.GetCharCount(Byte[], Int32, Int32)
w System.IO.Ports.SerialPort.InternalRead(Char[], Int32, Int32, Int32, Boolean)
w System.IO.Ports.SerialPort.ReadTo(System.String)
w System.IO.Ports.SerialPort.ReadLine()
w RanchoApp.Forms.ucPacking+<_serialPort_DataReceived>d__21.MoveNext()
w System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__6_1(System.Object)
w System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
w System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
w System.Threading.ThreadPoolWorkQueue.Dispatch()
w System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
Informacje o wyjątku: System.OverflowException
w System.IO.Ports.SerialPort.ReadTo(System.String)
w System.IO.Ports.SerialPort.ReadLine()
w RanchoApp.Forms.ucWeighting+<_serialPort_DataReceived>d__13.MoveNext()
w System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.<ThrowAsync>b__6_1(System.Object)
w System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
w System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
w System.Threading.ThreadPoolWorkQueue.Dispatch()
w System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
Informacje o wyjątku: System.ObjectDisposedException
w System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control, System.Delegate, System.Object[], Boolean)
w System.Windows.Forms.Control.Invoke(System.Delegate, System.Object[])
w RanchoApp.Forms.ucWeighting._serialPort_DataReceived(System.Object, System.IO.Ports.SerialDataReceivedEventArgs)
w System.IO.Ports.SerialPort.CatchReceivedEvents(System.Object, System.IO.Ports.SerialDataReceivedEventArgs)
w System.IO.Ports.SerialStream+EventLoopRunner.CallReceiveEvents(System.Object)
w System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
w System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
w System.Threading.ThreadPoolWorkQueue.Dispatch()
w System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

Informacje o wyjątku: System.IO.IOException
w System.IO.Ports.InternalResources.WinIOError(Int32, System.String)
w System.IO.Ports.SerialStream.EndRead(System.IAsyncResult)
w System.IO.Ports.SerialStream.Read(Byte[], Int32, Int32, Int32)
w System.IO.Ports.SerialStream.Read(Byte[], Int32, Int32)
w System.IO.Ports.SerialPort.InternalRead(Char[], Int32, Int32, Int32, Boolean)
w System.IO.Ports.SerialPort.ReadTo(System.String)
w System.IO.Ports.SerialPort.ReadLine()
w RanchoApp.Forms.ucPacking._serialPort_DataReceived(System.Object, System.IO.Ports.SerialDataReceivedEventArgs)
w System.IO.Ports.SerialPort.CatchReceivedEvents(System.Object, System.IO.Ports.SerialDataReceivedEventArgs)
w System.IO.Ports.SerialStream+EventLoopRunner.CallReceiveEvents(System.Object)
w System.Threading.QueueUserWorkItemCallback.WaitCallback_Context(System.Object)
w System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
w System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
w System.Threading.ThreadPoolWorkQueue.Dispatch()
w System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

Co ja jeszcze mogę zrobić żeby to działało stabilnie?

0

Metoda _serialPort_DataReceived nie musi a chyba nawet nie powinna być asynchroniczna bo i tak wywoływana jest w innym wątku - dlatego dane zwracasz delegatem.

Gdy zamykasz port brakuje SerialPortService.SerialPort.DataReceived -= _serialPort_DataReceived;
Jeżeli program zawiesza się przy zamykaniu aplikacji trzeba poczekać te 500ms aby metoda _serialPort_DataReceived zakończyła działanie.

Ja to w swoim programie do testów urządzeń modbus robię tak choć dalekie to pewnie od ideału ;)

public class ModBus
   {
	SerialPort Com;
       public ViewModel viewModel;
       delegate void ComDelegate(Byte[] mesage, int lenght);
	
 public ModBus()
	{	
		Com = new SerialPort("COM1", 9600, Parity.None, 8, StopBits.One);
           Com.ReadTimeout = 4;
           Com.WriteTimeout = 10;
	}
//```
	
	public void OpenPort()
       {
		if (!Com.IsOpen)
           {
			try
               {
                   Com.Open();
                   Com.DataReceived += new SerialDataReceivedEventHandler(ComDataReceived);
               }
               catch
               {
                   ...
               }
		}
	}
	
	public void ClosePort()
       {
           if (Com.IsOpen)
           {
               Com.DataReceived -= new SerialDataReceivedEventHandler(ComDataReceived);
               Com.Close();
           }
       }

       void ComDataReceived(object sender, SerialDataReceivedEventArgs e)
       {
           Thread.Sleep(500);
           if (!Com.IsOpen) return;
           int BytesReceived = Com.BytesToRead;
           Byte[] Received = new Byte[BytesReceived];
           Com.Read(Received, 0, BytesReceived);
           Application.Current.Dispatcher.Invoke(DispatcherPriority.Send, new ComDelegate(ReadRawData), Received, BytesReceived);
       }

       void ReadRawData(Byte[] message, int length)
       {
           Byte[] X = new Byte[length];
           ...
       }

       public void Send(Byte[] sendData, int lenght)
       {
           if (Com.IsOpen)
           {
               Com.Write(sendData, 0, length);
           }
       }
}
0

To jest ostatnia wersja jaką próbowałem, a próbowałem wszystkiego. Jeśli zrobię z delegate to dostaje wyjątki z IO, bez delegate dostaje, że kontrolka TextEdit is disposed, czyli próbuje ustawić tbWeight.Text w innym wątku i kółeczko się zamyka... I tak źle i tak nie dobrze. Szukałem bibliotek na nugecie, jedna działa tylko na Core, a ja muszę mieć .NET Framework, a druga nie poprawia tego co zepsute w .NET w SerialPort.

1 użytkowników online, w tym zalogowanych: 0, gości: 1