using System; using System.IO; using System.IO.Ports; using System.Text; using System.Threading.Tasks; namespace Elwig.Helpers.Weighing { public class SysTecITScale : Scale, ICommandScale { public string Manufacturer => "SysTec"; 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 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; Stream.WriteTimeout = 250; Stream.ReadTimeout = 11000; } protected async Task SendCommand(string command) { byte[] bytes = Encoding.ASCII.GetBytes($"<{command}>"); await Stream.WriteAsync(bytes); if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"<{command}>"); } protected async Task ReceiveResponse() { 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"); } 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[1..^3]; } protected async Task Weigh(bool incIdentNr) { 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"); var line = record[2..]; var status = line[ 0.. 2]; var date = line[ 2..10]; var time = line[10..15]; var identNr = line[15..19].Trim(); var scaleNr = line[19..20].Trim(); var brutto = line[20..28].Trim(); var tara = line[28..36].Trim(); var netto = line[36..44].Trim(); var unit = line[44..46]; var taraCode = line[46..48]; var zone = line[48..49].Trim(); var terminalNr = line[49..52].Trim(); 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])})"); } else if (unit != "kg") { throw new IOException($"Unsupported unit in weighing response: '{unit}'"); } identNr = identNr.Length > 0 && identNr != "0" ? identNr : null; var parsedDate = DateOnly.Parse(date); return new() { GrossWeight = int.Parse(brutto), TareWeight = int.Parse(tara), NetWeight = int.Parse(netto), WeighingId = identNr, FullWeighingId = identNr != null ? $"{parsedDate:yyyy-MM-dd}/{identNr}" : null, Date = parsedDate, Time = TimeOnly.Parse(time), }; } public async Task Weigh() { return await Weigh(true); } public async Task GetCurrentWeight() { return await Weigh(false); } public async Task Empty() { SerialPort? p = ControlSerialEmpty ?? Serial; if (EmptyMode == Output.RTS && p != null) { p.RtsEnable = true; await Task.Delay(EmptyDelay); p.RtsEnable = false; } else if (EmptyMode == Output.DTR && p != null) { p.DtrEnable = true; await Task.Delay(EmptyDelay); p.DtrEnable = false; } else if (EmptyMode == Output.OUT1 || EmptyMode == Output.OUT2) { int output = EmptyMode == Output.OUT1 ? 1 : 2; await SendCommand($"OS{output:00}"); await ReceiveResponse(); await Task.Delay(EmptyDelay); await SendCommand($"OC{output:00}"); await ReceiveResponse(); } } protected async Task SetFillingClearance(bool status) { SerialPort? p = ControlSerialFilling ?? Serial; if (FillingClearanceMode == Output.RTS && p != null) { p.RtsEnable = status; } else if (FillingClearanceMode == Output.DTR && p != null) { p.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:00}"); await ReceiveResponse(); } } public async Task GrantFillingClearance() { await SetFillingClearance(true); } public async Task RevokeFillingClearance() { await SetFillingClearance(false); } } }