From d2bc2f894ffdf4c91ae8387b4e3c9c0d3ffd40a4 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Sat, 3 Jan 2026 16:22:37 +0100 Subject: [PATCH] [#71] Weighing: Fix reconnection behaviour when COM port is connected/disconnected --- Elwig/App.xaml.cs | 32 ++++++++------ Elwig/Helpers/SerialPortWatcher.cs | 34 ++++++++------- Elwig/Helpers/Weighing/AveryEventScale.cs | 46 +++++++++++++++++---- Elwig/Helpers/Weighing/GassnerScale.cs | 8 ++-- Elwig/Helpers/Weighing/Scale.cs | 22 ++++++---- Elwig/Helpers/Weighing/SysTecITScale.cs | 18 ++++---- Elwig/Helpers/Weighing/WeighingException.cs | 6 +++ 7 files changed, 112 insertions(+), 54 deletions(-) create mode 100644 Elwig/Helpers/Weighing/WeighingException.cs diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs index 887a555..5732d6f 100644 --- a/Elwig/App.xaml.cs +++ b/Elwig/App.xaml.cs @@ -149,7 +149,7 @@ namespace Elwig { } catch (Exception e) { list.Add(new InvalidScale(s.Id)); if (s.Required) - MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagen-Fehler", + MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler", MessageBoxButton.OK, MessageBoxImage.Error); } } @@ -249,14 +249,18 @@ namespace Elwig { private void OnSerialPortConnected(object? sender, string name) { for (var i = 0; i < Config.Scales.Count; i++) { var s = Config.Scales[i]; - if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is InvalidScale) { - try { - Scales[i] = Scale.FromConfig(s); + if (s.Connection?.StartsWith($"serial://{name}:") ?? false) { + if (Scales[i] is InvalidScale) { + try { + Scales[i] = Scale.FromConfig(s); + MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information); + } catch (Exception e) { + Scales[i] = new InvalidScale(s.Id); + MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } else if (Scales[i] is IEventScale) { MessageBox.Show($"Verbindung zu Waage {s.Id} wieder hergestellt!", $"Waage {s.Id}", MessageBoxButton.OK, MessageBoxImage.Information); - } catch (Exception e) { - Scales[i] = new InvalidScale(s.Id); - MessageBox.Show($"Verbindung zu Waage {s.Id} konnte nicht hergestellt werden:\n\n{e.Message}", "Waagen-Fehler", - MessageBoxButton.OK, MessageBoxImage.Error); } } } @@ -268,12 +272,14 @@ namespace Elwig { var s = Config.Scales[i]; if ((s.Connection?.StartsWith($"serial://{name}:") ?? false) && Scales[i] is not InvalidScale) { MessageBox.Show($"Verbindung zu Waage {s.Id} unterbrochen!", $"Waagen {s.Id}", MessageBoxButton.OK, MessageBoxImage.Warning); - try { - Scales[i].Dispose(); - } catch { - // ignore + if (Scales[i] is ICommandScale) { + try { + Scales[i].Dispose(); + } catch { + // ignore + } + Scales[i] = new InvalidScale(s.Id); } - Scales[i] = new InvalidScale(s.Id); } } UpdateScales(); diff --git a/Elwig/Helpers/SerialPortWatcher.cs b/Elwig/Helpers/SerialPortWatcher.cs index d3acaeb..18ad3df 100644 --- a/Elwig/Helpers/SerialPortWatcher.cs +++ b/Elwig/Helpers/SerialPortWatcher.cs @@ -17,28 +17,34 @@ namespace Elwig.Helpers { public SerialPortWatcher() { _knownPorts = SerialPort.GetPortNames(); _deviceArrivalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2"); - _deviceArrivalWatcher.EventArrived += (s, e) => OnDeviceArrived(); + _deviceArrivalWatcher.EventArrived += OnDeviceArrived; _deviceRemovalWatcher = new ManagementEventWatcher("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3"); - _deviceRemovalWatcher.EventArrived += (s, e) => OnDeviceRemoved(); + _deviceRemovalWatcher.EventArrived += OnDeviceRemoved; _deviceArrivalWatcher.Start(); _deviceRemovalWatcher.Start(); } - private void OnDeviceArrived() { - string[] currentPorts = SerialPort.GetPortNames(); - var newPorts = currentPorts.Except(_knownPorts).ToArray(); - foreach (var port in newPorts) - SerialPortConnected?.Invoke(this, port); - _knownPorts = currentPorts; + private void OnDeviceArrived(object sender, EventArrivedEventArgs evt) { + App.MainDispatcher.Invoke(() => { + // "synchronized" + string[] currentPorts = SerialPort.GetPortNames(); + var newPorts = currentPorts.Except(_knownPorts).ToArray(); + foreach (var port in newPorts) + SerialPortConnected?.Invoke(this, port); + _knownPorts = currentPorts; + }); } - private void OnDeviceRemoved() { - string[] currentPorts = SerialPort.GetPortNames(); - var removedPorts = _knownPorts.Except(currentPorts).ToArray(); - foreach (var port in removedPorts) - SerialPortDisconnected?.Invoke(this, port); - _knownPorts = currentPorts; + private void OnDeviceRemoved(object sender, EventArrivedEventArgs evt) { + App.MainDispatcher.Invoke(() => { + // "synchronized" + string[] currentPorts = SerialPort.GetPortNames(); + var removedPorts = _knownPorts.Except(currentPorts).ToArray(); + foreach (var port in removedPorts) + SerialPortDisconnected?.Invoke(this, port); + _knownPorts = currentPorts; + }); } public void Dispose() { diff --git a/Elwig/Helpers/Weighing/AveryEventScale.cs b/Elwig/Helpers/Weighing/AveryEventScale.cs index de7513b..80fe167 100644 --- a/Elwig/Helpers/Weighing/AveryEventScale.cs +++ b/Elwig/Helpers/Weighing/AveryEventScale.cs @@ -15,15 +15,17 @@ namespace Elwig.Helpers.Weighing { public bool IsReady { get; private set; } public bool HasFillingClearance { get; private set; } - public event IEventScale.EventHandler WeighingEvent; + public event IEventScale.EventHandler? WeighingEvent; private bool IsRunning = true; private readonly Thread BackgroundThread; + private readonly string Connection; - public AveryEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) : - base(cnx, empty, filling, limit, log) { + public AveryEventScale(string id, string model, string cnx, string? log = null, bool required = true) : + base(cnx, null, null, null, log, true, !required) { ScaleId = id; Model = model; + Connection = cnx; IsReady = true; HasFillingClearance = false; Stream.WriteTimeout = -1; @@ -50,19 +52,49 @@ namespace Elwig.Helpers.Weighing { var data = await Receive(); if (data != null) RaiseWeighingEvent(new WeighingEventArgs(data.Value)); + } catch (ThreadInterruptedException) { + // ignore + } catch (IOException) { + await Task.Delay(500); + await Reconnect(); + } catch (TimeoutException) { + await Task.Delay(500); + await Reconnect(); } catch (Exception ex) { - MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler", + MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message} ({ex.GetType().Name})", "Waagenfehler", MessageBoxButton.OK, MessageBoxImage.Error); } } } + protected async Task Reconnect() { + try { Reader.Close(); } catch { } + try { Stream.Close(); } catch { } + try { Serial?.Close(); } catch { } + while (IsRunning) { + try { + if (Connection.StartsWith("serial:")) { + Serial = Utils.OpenSerialConnection(Connection); + Stream = Serial.BaseStream; + } else if (Connection.StartsWith("tcp:")) { + Tcp = Utils.OpenTcpConnection(Connection); + Stream = Tcp.GetStream(); + } + Reader = new(Stream, Encoding.ASCII, false, 512); + break; + } catch { + // ignore + } + await Task.Delay(1000); + } + } + protected async Task Receive() { var line = ""; while (line.Length < 33) { var ch = Reader.Read(); if (ch == -1) { - return null; + throw new IOException("Connection closed"); } else if (line.Length > 0 || ch == ' ') { line += char.ToString((char)ch); } @@ -71,7 +103,7 @@ namespace Elwig.Helpers.Weighing { if (line == null || line == "") { return null; } else if (line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') { - throw new IOException($"Invalid event from scale: '{line}'"); + throw new FormatException($"Invalid event from scale: '{line}'"); } var date = line[ 1.. 9]; @@ -81,7 +113,7 @@ namespace Elwig.Helpers.Weighing { var unit = line[30..32]; if (unit != "kg") { - throw new IOException($"Unsupported unit in weighing event: '{unit}'"); + throw new WeighingException($"Unsupported unit in weighing event: '{unit}'"); } identNr = identNr.Length > 0 && identNr != "0" ? identNr : null; diff --git a/Elwig/Helpers/Weighing/GassnerScale.cs b/Elwig/Helpers/Weighing/GassnerScale.cs index 1c48a9e..a6ff07d 100644 --- a/Elwig/Helpers/Weighing/GassnerScale.cs +++ b/Elwig/Helpers/Weighing/GassnerScale.cs @@ -36,7 +36,7 @@ namespace Elwig.Helpers.Weighing { var line = await Reader.ReadUntilAsync('\x03'); if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n"); if (line == null || line.Length < 4 || !line.StartsWith('\x02')) { - throw new IOException("Invalid response from scale"); + throw new FormatException("Invalid response from scale"); } var status = line[1..3]; @@ -45,9 +45,9 @@ namespace Elwig.Helpers.Weighing { switch (status[1]) { case 'M': msg = "Waage in Bewegung"; break; } - throw new IOException($"Waagenfehler {status}: {msg}"); + throw new WeighingException($"Waagenfehler {status}: {msg}"); } else if (status[0] != ' ') { - throw new IOException($"Invalid response from scale (error code {status})"); + throw new WeighingException($"Invalid response from scale (error code {status})"); } return line[1..^1]; @@ -57,7 +57,7 @@ namespace Elwig.Helpers.Weighing { await SendCommand(incIdentNr ? '\x05' : '?'); string record = await ReceiveResponse(); if (record.Length != 45) - throw new IOException("Invalid response from scale: Received record has invalid size"); + throw new FormatException("Invalid response from scale: Received record has invalid size"); var line = record[2..]; var brutto = line[ 0.. 7].Trim(); diff --git a/Elwig/Helpers/Weighing/Scale.cs b/Elwig/Helpers/Weighing/Scale.cs index 08e5b47..f387cfa 100644 --- a/Elwig/Helpers/Weighing/Scale.cs +++ b/Elwig/Helpers/Weighing/Scale.cs @@ -1,8 +1,9 @@ -using System.IO.Ports; -using System.IO; -using System.Net.Sockets; using System; +using System.IO; +using System.IO.Ports; +using System.Net.Sockets; using System.Text; +using System.Windows; namespace Elwig.Helpers.Weighing { public abstract class Scale : IDisposable { @@ -27,7 +28,7 @@ namespace Elwig.Helpers.Weighing { if (config.Type == "SysTec-IT") { return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); } else if (config.Type == "Avery-Async") { - return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); + return new AveryEventScale(config.Id, config.Model!, config.Connection!, config.Log, config.Required); } else if (config.Type == "Gassner") { return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log); } else { @@ -35,10 +36,17 @@ namespace Elwig.Helpers.Weighing { } } - protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) { + protected Scale(string cnx, string? empty, string? filling, int? limit, string? log, bool softFail = false, bool failSilent = false) { if (cnx.StartsWith("serial:")) { - Serial = Utils.OpenSerialConnection(cnx); - Stream = Serial.BaseStream; + try { + Serial = Utils.OpenSerialConnection(cnx); + } catch (Exception e) { + if (!softFail) throw; + if (!failSilent) + MessageBox.Show($"Verbindung zu Waage konnte nicht hergestellt werden:\n\n{e.Message}", "Waagenfehler", + MessageBoxButton.OK, MessageBoxImage.Warning); + } + Stream = Serial?.BaseStream ?? Stream.Null; } else if (cnx.StartsWith("tcp:")) { Tcp = Utils.OpenTcpConnection(cnx); Stream = Tcp.GetStream(); diff --git a/Elwig/Helpers/Weighing/SysTecITScale.cs b/Elwig/Helpers/Weighing/SysTecITScale.cs index 420ed2a..b3ffd4a 100644 --- a/Elwig/Helpers/Weighing/SysTecITScale.cs +++ b/Elwig/Helpers/Weighing/SysTecITScale.cs @@ -34,14 +34,14 @@ namespace Elwig.Helpers.Weighing { var line = await Reader.ReadUntilAsync("\r\n"); if (LogPath != null) await File.AppendAllTextAsync(LogPath, line); if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) { - throw new IOException("Invalid response from scale"); + throw new FormatException("Invalid response from scale"); } var error = line[1..3]; string msg = $"Unbekannter Fehler (Fehler code {error})"; if (error[0] == '0') { if (error[1] != '0') { - throw new IOException($"Invalid response from scale (error code {error})"); + throw new WeighingException($"Invalid response from scale (error code {error})"); } } else if (error[0] == '1') { switch (error[1]) { @@ -52,21 +52,21 @@ namespace Elwig.Helpers.Weighing { case '6': msg = "Drucker nicht bereit"; break; case '7': msg = "Druckmuster enthält ungültiges Kommando"; break; } - throw new IOException($"Waagenfehler {error}: {msg}"); + throw new WeighingException($"Waagenfehler {error}: {msg}"); } else if (error[0] == '2') { switch (error[1]) { case '0': msg = "Brutto negativ"; break; } - throw new IOException($"Fehler {error}: {msg}"); + throw new WeighingException($"Fehler {error}: {msg}"); } else if (error[0] == '3') { switch (error[1]) { case '1': msg = "Übertragunsfehler"; break; case '2': msg = "Ungültiger Befehl"; break; case '3': msg = "Ungültiger Parameter"; break; } - throw new IOException($"Kommunikationsfehler {error}: {msg}"); + throw new WeighingException($"Kommunikationsfehler {error}: {msg}"); } else { - throw new IOException($"Invalid response from scale (error code {error})"); + throw new WeighingException($"Invalid response from scale (error code {error})"); } return line[1..^3]; @@ -76,7 +76,7 @@ namespace Elwig.Helpers.Weighing { await SendCommand(incIdentNr ? $"RN{InternalScaleNr}" : $"RM{InternalScaleNr}"); string record = await ReceiveResponse(); if (record.Length != 62) - throw new IOException("Invalid response from scale: Received record has invalid size"); + throw new FormatException("Invalid response from scale: Received record has invalid size"); var line = record[2..]; var status = line[ 0.. 2]; @@ -94,9 +94,9 @@ namespace Elwig.Helpers.Weighing { var crc16 = line[52..60].Trim(); if (Utils.CalcCrc16Modbus(record[..54]) != ushort.Parse(crc16)) { - throw new IOException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})"); + throw new WeighingException($"Invalid response from scale: Invalid CRC16 checksum ({crc16} != {Utils.CalcCrc16Modbus(record[..54])})"); } else if (unit != "kg") { - throw new IOException($"Unsupported unit in weighing response: '{unit}'"); + throw new WeighingException($"Unsupported unit in weighing response: '{unit}'"); } identNr = identNr.Length > 0 && identNr != "0" ? identNr : null; diff --git a/Elwig/Helpers/Weighing/WeighingException.cs b/Elwig/Helpers/Weighing/WeighingException.cs new file mode 100644 index 0000000..856c28d --- /dev/null +++ b/Elwig/Helpers/Weighing/WeighingException.cs @@ -0,0 +1,6 @@ +using System; + +namespace Elwig.Helpers.Weighing { + class WeighingException(string? message = null) : Exception(message) { + } +}