From 99ca12b2769d761300d7b96d52107874eb774bc7 Mon Sep 17 00:00:00 2001
From: Lorenz Stechauner <lorenz.stechauner@necronda.net>
Date: Wed, 21 Feb 2024 12:57:55 +0100
Subject: [PATCH] Weighing: Restructure class structure

---
 Elwig/App.xaml.cs                             | 22 +----
 Elwig/Helpers/Config.cs                       | 47 ++++++---
 Elwig/Helpers/Weighing/Scale.cs               | 95 +++++++++++++++++++
 Elwig/Helpers/Weighing/SchemberEventScale.cs  | 22 +++++
 .../{SystecScale.cs => SystecItScale.cs}      | 77 +--------------
 Tests/WeighingTests/ScaleTestMatzen.cs        |  2 +-
 6 files changed, 161 insertions(+), 104 deletions(-)
 create mode 100644 Elwig/Helpers/Weighing/Scale.cs
 create mode 100644 Elwig/Helpers/Weighing/SchemberEventScale.cs
 rename Elwig/Helpers/Weighing/{SystecScale.cs => SystecItScale.cs} (67%)

diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs
index 54d6490..08be304 100644
--- a/Elwig/App.xaml.cs
+++ b/Elwig/App.xaml.cs
@@ -63,7 +63,7 @@ namespace Elwig {
             Directory.CreateDirectory(TempPath);
             Directory.CreateDirectory(DataPath);
             MainDispatcher = Dispatcher;
-            Scales = Array.Empty<IScale>();
+            Scales = [];
             CurrentApp = this;
             OverrideCulture();
         }
@@ -96,7 +96,7 @@ namespace Elwig {
                 return;
             }
 
-            Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = new();
+            Dictionary<string, (string, string, int?, string?, string?, string?, string?, string?)> branches = [];
             using (var ctx = new AppDbContext()) {
                 branches = ctx.Branches.ToDictionary(b => b.Name.ToLower(), b => (b.ZwstId, b.Name, b.PostalDest?.AtPlz?.Plz, b.PostalDest?.AtPlz?.Ort.Name, b.Address, b.PhoneNr, b.FaxNr, b.MobileNr));
                 try {
@@ -115,23 +115,11 @@ namespace Elwig {
 
             var list = new List<IScale>();
             foreach (var s in Config.Scales) {
-                var id = s[0];
                 try {
-                    var type = s[1]?.ToLower();
-                    var model = s[2];
-                    var cnx = s[3];
-                    var empty = s[4];
-                    var filling = s[5];
-                    int? limit = s[6] == null ? null : int.Parse(s[6]);
-                    var log = s[7];
-                    if (type == "systec") {
-                        list.Add(new SystecScale(id, model, cnx, empty, filling, limit, log));
-                    } else {
-                        throw new ArgumentException($"Invalid scale type: \"{type}\"");
-                    }
+                    list.Add(Scale.FromConfig(s));
                 } catch (Exception e) {
-                    list.Add(new InvalidScale(id));
-                    MessageBox.Show($"Unable to create scale {s[0]}:\n\n{e.Message}", "Scale Error", MessageBoxButton.OK, MessageBoxImage.Error);
+                    list.Add(new InvalidScale(s.Id));
+                    MessageBox.Show($"Unable to create scale {s.Id}:\n\n{e.Message}", "Scale Error", MessageBoxButton.OK, MessageBoxImage.Error);
                 }
             }
             Scales = list;
diff --git a/Elwig/Helpers/Config.cs b/Elwig/Helpers/Config.cs
index 8cf601f..f967105 100644
--- a/Elwig/Helpers/Config.cs
+++ b/Elwig/Helpers/Config.cs
@@ -4,6 +4,31 @@ using System.Linq;
 using Microsoft.Extensions.Configuration;
 
 namespace Elwig.Helpers {
+
+    public record struct ScaleConfig {
+        public string Id;
+        public string? Type;
+        public string? Model;
+        public string? Connection;
+        public string? Empty;
+        public string? Filling;
+        public string? Limit;
+        public string? Log;
+        public string? _Log;
+
+        public ScaleConfig(string id, string? type, string? model, string? cnx, string? empty, string? filling, string? limit, string? log) {
+            Id = id;
+            Type = type;
+            Model = model;
+            Connection = cnx;
+            Empty = empty;
+            Filling = filling;
+            Limit = limit;
+            _Log = log;
+            Log = log != null ? Path.Combine(App.DataPath, log) : null;
+        }
+    }
+
     public class Config {
 
         private readonly string FileName;
@@ -11,8 +36,8 @@ namespace Elwig.Helpers {
         public string DatabaseFile = App.DataPath + "database.sqlite3";
         public string? DatabaseLog = null;
         public string? Branch = null;
-        public IList<string?[]> Scales;
-        private readonly List<string?[]> ScaleList = [];
+        public IList<ScaleConfig> Scales;
+        private readonly List<ScaleConfig> ScaleList = [];
         private static readonly string[] trueValues = ["1", "true", "yes", "on"];
 
         public Config(string filename) {
@@ -34,12 +59,10 @@ namespace Elwig.Helpers {
             ScaleList.Clear();
             Scales = ScaleList;
             foreach (var s in scales) {
-                string? scaleLog = config[$"scale.{s}:log"];
-                if (scaleLog != null) scaleLog = Path.Combine(App.DataPath, scaleLog);
-                ScaleList.Add([
+                ScaleList.Add(new(
                     s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
-                    config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], scaleLog
-                ]);
+                    config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], config[$"scale.{s}:log"]
+                ));
             }
         }
 
@@ -51,11 +74,11 @@ namespace Elwig.Helpers {
             file.Write($"\r\n[database]\r\nfile = {DatabaseFile}\r\n");
             if (DatabaseLog != null) file.Write($"log = {DatabaseLog}\r\n");
             foreach (var s in ScaleList) {
-                file.Write($"\r\n[scale.{s[0]}]\r\ntype = {s[1]}\r\nmodel = {s[2]}\r\nconnection = {s[3]}\r\n");
-                if (s[4] != null) file.Write($"empty = {s[4]}\r\n");
-                if (s[5] != null) file.Write($"filling = {s[5]}\r\n");
-                if (s[6] != null) file.Write($"limit = {s[6]}\r\n");
-                if (s[7] != null) file.Write($"log = {s[7]}\r\n");
+                file.Write($"\r\n[scale.{s.Id}]\r\ntype = {s.Type}\r\nmodel = {s.Model}\r\nconnection = {s.Connection}\r\n");
+                if (s.Empty != null) file.Write($"empty = {s.Empty}\r\n");
+                if (s.Filling != null) file.Write($"filling = {s.Filling}\r\n");
+                if (s.Limit != null) file.Write($"limit = {s.Limit}\r\n");
+                if (s._Log != null) file.Write($"log = {s._Log}\r\n");
             }
         }
     }
diff --git a/Elwig/Helpers/Weighing/Scale.cs b/Elwig/Helpers/Weighing/Scale.cs
new file mode 100644
index 0000000..88c446b
--- /dev/null
+++ b/Elwig/Helpers/Weighing/Scale.cs
@@ -0,0 +1,95 @@
+using System.IO.Ports;
+using System.IO;
+using System.Net.Sockets;
+using System;
+
+namespace Elwig.Helpers.Weighing {
+    public abstract class Scale : IDisposable {
+
+        protected enum Output { RTS, DTR, OUT1, OUT2 };
+
+        protected SerialPort? Serial = null;
+        protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
+        protected TcpClient? Tcp = null;
+        protected Stream Stream;
+
+        protected readonly Output? EmptyMode = null;
+        protected readonly Output? FillingClearanceMode = null;
+        protected readonly int EmptyDelay;
+
+        public int? WeightLimit { get; private set; }
+        public string? LogPath { get; private set; }
+
+        public static IScale FromConfig(ScaleConfig config) {
+            int? limit = config.Limit != null ? int.Parse(config.Limit) : null;
+            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 == "Schember-evt") {
+                return new SchemberEventScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
+            } else {
+                throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
+            }
+        }
+
+        protected Scale(string cnx, string? empty, string? filling, int? limit, string? log) {
+            if (cnx.StartsWith("serial:")) {
+                Serial = Utils.OpenSerialConnection(cnx);
+                Stream = Serial.BaseStream;
+            } else if (cnx.StartsWith("tcp:")) {
+                Tcp = Utils.OpenTcpConnection(cnx);
+                Stream = Tcp.GetStream();
+            } else {
+                throw new ArgumentException("Unsupported scheme");
+            }
+
+            LogPath = log;
+
+            if (empty != null) {
+                var parts = empty.Split(':');
+                if (parts.Length == 3) {
+                    if (parts[0] != Serial?.PortName)
+                        ControlSerialEmpty = Utils.OpenSerialConnection($"serial://{parts[0]}:9600");
+                } else if (parts.Length != 2) {
+                    throw new ArgumentException("Invalid value for 'empty'");
+                }
+                EmptyMode = ConvertOutput(parts[^2]);
+                EmptyDelay = int.Parse(parts[^1]);
+            }
+
+            WeightLimit = limit;
+            if (filling != null) {
+                var parts = filling.Split(':');
+                if (parts.Length == 2) {
+                    if (parts[0] != Serial?.PortName)
+                        ControlSerialFilling = parts[0] != ControlSerialEmpty?.PortName ? Utils.OpenSerialConnection($"serial://{parts[0]}:9600") : ControlSerialEmpty;
+                } else if (parts.Length != 1) {
+                    throw new ArgumentException("Invalid value for 'filling'");
+                }
+                FillingClearanceMode = ConvertOutput(parts[^1]);
+            }
+
+            if (FillingClearanceMode != null && WeightLimit == null)
+                throw new ArgumentException("Weight limit has to be set, if filling clearance supervision is enabled");
+        }
+
+        public void Dispose() {
+            Stream.Close();
+            Serial?.Close();
+            ControlSerialEmpty?.Close();
+            ControlSerialFilling?.Close();
+            Tcp?.Close();
+            GC.SuppressFinalize(this);
+        }
+
+        protected static Output? ConvertOutput(string? value) {
+            return value switch {
+                null => null,
+                "RTS" => Output.RTS,
+                "DTR" => Output.DTR,
+                "OUT1" => Output.OUT1,
+                "OUT2" => Output.OUT2,
+                _ => throw new ArgumentException($"Invalid value for argument: '{value}'"),
+            };
+        }
+    }
+}
diff --git a/Elwig/Helpers/Weighing/SchemberEventScale.cs b/Elwig/Helpers/Weighing/SchemberEventScale.cs
new file mode 100644
index 0000000..88559e3
--- /dev/null
+++ b/Elwig/Helpers/Weighing/SchemberEventScale.cs
@@ -0,0 +1,22 @@
+namespace Elwig.Helpers.Weighing {
+    public class SchemberEventScale : Scale, IEventScale {
+
+        public string Manufacturer => "Schember";
+        public int InternalScaleNr => 1;
+        public string Model { get; private set; }
+        public string ScaleId { get; private set; }
+        public bool IsReady { get; private set; }
+        public bool HasFillingClearance { get; private set; }
+
+        public SchemberEventScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
+            base(cnx, empty, filling, limit, log) {
+            ScaleId = id;
+            Model = model;
+            IsReady = true;
+            HasFillingClearance = false;
+            Stream.WriteTimeout = 250;
+            Stream.ReadTimeout = 6000;
+        }
+
+    }
+}
diff --git a/Elwig/Helpers/Weighing/SystecScale.cs b/Elwig/Helpers/Weighing/SystecItScale.cs
similarity index 67%
rename from Elwig/Helpers/Weighing/SystecScale.cs
rename to Elwig/Helpers/Weighing/SystecItScale.cs
index ec34e09..ec0b96b 100644
--- a/Elwig/Helpers/Weighing/SystecScale.cs
+++ b/Elwig/Helpers/Weighing/SystecItScale.cs
@@ -1,23 +1,11 @@
 using System;
 using System.IO;
 using System.IO.Ports;
-using System.Net.Sockets;
 using System.Text;
 using System.Threading.Tasks;
 
 namespace Elwig.Helpers.Weighing {
-    public class SystecScale : ICommandScale {
-
-        protected enum Output { RTS, DTR, OUT1, OUT2 };
-
-        protected SerialPort? Serial = null;
-        protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
-        protected TcpClient? Tcp = null;
-        protected Stream Stream;
-
-        protected readonly Output? EmptyMode = null;
-        protected readonly Output? FillingClearanceMode = null;
-        protected readonly int EmptyDelay;
+    public class SysTecITScale : Scale, ICommandScale {
 
         public string Manufacturer => "SysTec";
         public int InternalScaleNr => 1;
@@ -25,74 +13,15 @@ namespace Elwig.Helpers.Weighing {
         public string ScaleId { get; private set; }
         public bool IsReady { get; private set; }
         public bool HasFillingClearance { get; private set; }
-        public int? WeightLimit { get; private set; }
-        public string? LogPath { get; private set; }
 
-        public SystecScale(string id, string model, string connection, string? empty = null, string? filling = null, int? limit = null, string? log = null) {
+        public SysTecITScale(string id, string model, string cnx, string? empty = null, string? filling = null, int? limit = null, string? log = null) :
+            base(cnx, empty, filling, limit, log) {
             ScaleId = id;
             Model = model;
             IsReady = true;
             HasFillingClearance = false;
-            LogPath = log;
-
-            if (connection.StartsWith("serial:")) {
-                Serial = Utils.OpenSerialConnection(connection);
-                Stream = Serial.BaseStream;
-            } else if (connection.StartsWith("tcp:")) {
-                Tcp = Utils.OpenTcpConnection(connection);
-                Stream = Tcp.GetStream();
-            } else {
-                throw new ArgumentException("Unsupported scheme");
-            }
             Stream.WriteTimeout = 250;
             Stream.ReadTimeout = 11000;
-
-            if (empty != null) {
-                var parts = empty.Split(':');
-                if (parts.Length == 3) {
-                    if (parts[0] != Serial?.PortName)
-                        ControlSerialEmpty = Utils.OpenSerialConnection($"serial://{parts[0]}:9600");
-                } else if (parts.Length != 2) {
-                    throw new ArgumentException("Invalid value for 'empty'");
-                }
-                EmptyMode = ConvertOutput(parts[^2]);
-                EmptyDelay = int.Parse(parts[^1]);
-            }
-
-            WeightLimit = limit;
-            if (filling != null) {
-                var parts = filling.Split(':');
-                if (parts.Length == 2) {
-                    if (parts[0] != Serial?.PortName)
-                        ControlSerialFilling = parts[0] != ControlSerialEmpty?.PortName ? Utils.OpenSerialConnection($"serial://{parts[0]}:9600") : ControlSerialEmpty;
-                } else if (parts.Length != 1) {
-                    throw new ArgumentException("Invalid value for 'filling'");
-                }
-                FillingClearanceMode = ConvertOutput(parts[^1]);
-            }
-
-            if (FillingClearanceMode != null && WeightLimit == null)
-                throw new ArgumentException("Weight limit has to be set, if filling clearance supervision is enabled");
-        }
-
-        public void Dispose() {
-            Stream.Close();
-            Serial?.Close();
-            ControlSerialEmpty?.Close();
-            ControlSerialFilling?.Close();
-            Tcp?.Close();
-            GC.SuppressFinalize(this);
-        }
-
-        protected static Output? ConvertOutput(string? value) {
-            return value switch {
-                null => null,
-                "RTS" => Output.RTS,
-                "DTR" => Output.DTR,
-                "OUT1" => Output.OUT1,
-                "OUT2" => Output.OUT2,
-                _ => throw new ArgumentException($"Invalid value for argument: '{value}'"),
-            };
         }
 
         protected async Task SendCommand(string command) {
diff --git a/Tests/WeighingTests/ScaleTestMatzen.cs b/Tests/WeighingTests/ScaleTestMatzen.cs
index 3ef4f8a..dfc7d32 100644
--- a/Tests/WeighingTests/ScaleTestMatzen.cs
+++ b/Tests/WeighingTests/ScaleTestMatzen.cs
@@ -5,7 +5,7 @@ namespace Tests.WeighingTests {
     class ScaleTestMatzen {
 
         private MockScale? Mock;
-        private SystecScale? Scale;
+        private SysTecITScale? Scale;
 
         private static (string, bool) ScaleHandler(string req, int weight, string? error, int identNr) {
             var modes = error?.Split(';') ?? [];