using System; using System.IO; using System.IO.Ports; using System.Net.Sockets; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Elwig.Helpers.Weighing { public class SystecScale : IScale { protected enum Output { RTS, DTR, OUT1, OUT2 }; protected SerialPort? Serial = null; protected TcpClient? Tcp = null; protected StreamReader Reader; protected StreamWriter Writer; protected readonly Output? EmptyMode = null; protected readonly Output? FillingClearanceMode = null; protected readonly int EmptyDelay; protected static readonly Regex SerialRe = new(@"^serial://([A-Za-z0-9]+):([0-9]+)(,([5-9]),([NOEMSnoems]),(0|1|1\.5|2|))?$", RegexOptions.Compiled); protected static readonly Regex TcpRe = new(@"^tcp://[A-Za-z0-9:._-]+(:[0-9]+)?$", RegexOptions.Compiled); public string Manufacturer => "SysTec"; public int InternalScaleNr => 1; public string Model { get; private set; } public int ScaleNr { get; private set; } public bool IsReady { get; private set; } public bool HasFillingClearance { get; private set; } public int? WeightLimit { get; private set; } public SystecScale(int scaleNr, string model, string connection, string? empty = null, string? fill = null, int? limit = null) { ScaleNr = scaleNr; Model = model; IsReady = true; HasFillingClearance = false; Stream stream; if (connection.StartsWith("serial:")) { var m = SerialRe.Match(connection); if (!m.Success) throw new ArgumentException("Invalid connection string for scheme \"serial\""); var stop = m.Groups[6].Value; var parity = m.Groups[5].Value.ToUpper(); var data = m.Groups[4].Value; Serial = new() { PortName = m.Groups[1].Value, BaudRate = int.Parse(m.Groups[2].Value), Parity = parity == "E" ? Parity.Even : parity == "O" ? Parity.Odd : parity == "M" ? Parity.Mark : parity == "S" ? Parity.Space : Parity.None, DataBits = data == "" ? 8 : int.Parse(data), StopBits = (StopBits)(stop == "" ? 1 : stop == "1.5" ? 3 : stop[0] - '0'), Handshake = Handshake.None, }; Serial.Open(); stream = Serial.BaseStream; } else if (connection.StartsWith("tcp:")) { var m = TcpRe.Match(connection); if (!m.Success) throw new ArgumentException("Invalid connection string for scheme \"tcp\""); Tcp = new() { SendTimeout = 250, ReceiveTimeout = 250, }; Tcp.Connect(m.Groups[1].Value, int.Parse(m.Groups[2].Value)); stream = Tcp.GetStream(); } else { throw new ArgumentException("Unsupported scheme"); } if (empty != null) { var parts = empty.Split(':'); EmptyMode = ConvertOutput(parts[0]); EmptyDelay = int.Parse(parts[1]); } FillingClearanceMode = ConvertOutput(fill); WeightLimit = limit; if (FillingClearanceMode != null && WeightLimit == null) throw new ArgumentException("Weight limit has to be set, if filling clearance supervision is enalbed"); Writer = new(stream, Encoding.ASCII, -1, true); Reader = new(stream, Encoding.ASCII, false, -1, true); } public void Dispose() { Writer.Close(); Reader.Close(); Serial?.Close(); Tcp?.Close(); GC.SuppressFinalize(this); } protected static Output? ConvertOutput(string? value) { if (value == null) return null; value = value.ToUpper(); if (value == "RTS") { return Output.RTS; } else if (value == "DTR") { return Output.DTR; } else if (value == "OUT1") { return Output.OUT1; } else if (value == "OUT2") { return Output.OUT2; } throw new ArgumentException("Invalid value for argument empty"); } public async Task SendCommand(string command) { await Writer.WriteAsync($"<{command}>"); } public async Task ReceiveResponse() { var line = await Reader.ReadLineAsync(); if (line == null || line.Length < 4 || !line.StartsWith("<") || !line.EndsWith(">")) { throw new IOException("Invalid response from scale"); } var error = line[1..3]; if (error[0] == '0') { if (error[1] != '0') { throw new IOException($"Invalid response from scale (error code {error}"); } } else if (error[0] == '1') { string msg = $"Unbekannter Fehler (Fehler code {error})"; switch (error[1]) { case '1': msg = "Allgemeiner Waagenfehler"; break; case '2': msg = "Waage in Überlast"; break; case '3': msg = "Waage in Bewegung"; break; case '5': msg = "Tarierungs- oder Nullsetzfehler"; break; case '6': msg = "Drucker nicht bereit"; break; case '7': msg = "Druckmuster enthält ungültiges Kommando"; break; } throw new IOException($"Waagenfehler {error}: {msg}"); } else if (error[0] == '3') { string msg = $"Unbekannter Fehler (Fehler code {error})"; 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}"); } else { throw new IOException($"Invalid response from scale (error code {error})"); } return line[3..(line.Length - 1)]; } protected async Task Weigh(bool incIdentNr) { await SendCommand(incIdentNr ? $"RM{InternalScaleNr}" : $"RN{InternalScaleNr}"); string line = await ReceiveResponse(); var status = line[ 0.. 2]; var date = line[ 2..10]; var time = line[10..15]; var identNr = line[15..19]; var scaleNr = line[19..20]; var brutto = line[20..28]; var tara = line[28..36]; var netto = line[36..44]; var unit = line[44..46]; var taraCode = line[46..48]; var zone = line[48..49]; var terminalNr = line[49..52]; var crc16 = line[52..60]; return new() { Weight = int.TryParse(netto.Trim(), out int w) ? w : null, WeighingId = identNr.Trim().Length > 0 ? identNr.Trim() : null, Date = date, Time = time, }; } public async Task Weigh() { return await Weigh(true); } public async Task GetCurrentWeight() { return await Weigh(false); } public async Task Empty() { if (EmptyMode == Output.RTS && Serial != null) { Serial.RtsEnable = true; await Task.Delay(EmptyDelay); Serial.RtsEnable = false; } else if (EmptyMode == Output.DTR && Serial != null) { Serial.DtrEnable = true; await Task.Delay(EmptyDelay); Serial.DtrEnable = false; } else if (EmptyMode == Output.OUT1 || EmptyMode == Output.OUT2) { int output = EmptyMode == Output.OUT1 ? 1 : 2; await SendCommand($"OS{output:02i}"); await ReceiveResponse(); await Task.Delay(EmptyDelay); await SendCommand($"OC{output:02i}"); await ReceiveResponse(); } } protected async Task SetFillingClearance(bool status) { if (FillingClearanceMode == Output.RTS && Serial != null) { Serial.RtsEnable = status; } else if (FillingClearanceMode == Output.DTR && Serial != null) { Serial.DtrEnable = status; } else if (FillingClearanceMode == Output.OUT1 || FillingClearanceMode == Output.OUT2) { string cmd = status ? "OS" : "OC"; int output = FillingClearanceMode == Output.OUT1 ? 1 : 2; await SendCommand($"{cmd}{output:02i}"); await ReceiveResponse(); } } public async Task GrantFillingClearance() { await SetFillingClearance(true); } public async Task RevokeFillingClearance() { await SetFillingClearance(false); } } }