Weighing: Add Gassner scale and update tests
This commit is contained in:
63
Elwig/Helpers/Extensions.cs
Normal file
63
Elwig/Helpers/Extensions.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers {
|
||||
static partial class Extensions {
|
||||
|
||||
[LibraryImport("msvcrt.dll")]
|
||||
[UnmanagedCallConv(CallConvs = [typeof(System.Runtime.CompilerServices.CallConvCdecl)])]
|
||||
private static unsafe partial int memcmp(void* b1, void* b2, long count);
|
||||
|
||||
public static unsafe int CompareBuffers(char[] buffer1, int offset1, char[] buffer2, int offset2, int count) {
|
||||
fixed (char* b1 = buffer1, b2 = buffer2) {
|
||||
return memcmp(b1 + offset1, b2 + offset2, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static string? ReadUntil(this StreamReader reader, string delimiter) {
|
||||
return ReadUntil(reader, delimiter.ToCharArray());
|
||||
}
|
||||
|
||||
public static string? ReadUntil(this StreamReader reader, char delimiter) {
|
||||
return ReadUntil(reader, new char[] { delimiter });
|
||||
}
|
||||
|
||||
public static string? ReadUntil(this StreamReader reader, char[] delimiter) {
|
||||
var buf = new char[512];
|
||||
int bufSize = 0, ret;
|
||||
while (!reader.EndOfStream && bufSize < buf.Length - 1) {
|
||||
if ((ret = reader.Read()) == -1)
|
||||
return null;
|
||||
buf[bufSize++] = (char)ret;
|
||||
|
||||
if (bufSize >= delimiter.Length && CompareBuffers(buf, bufSize - delimiter.Length, delimiter, 0, delimiter.Length) == 0)
|
||||
return new string(buf, 0, bufSize);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Task<string?> ReadUntilAsync(this StreamReader reader, string delimiter) {
|
||||
return ReadUntilAsync(reader, delimiter.ToCharArray());
|
||||
}
|
||||
|
||||
public static Task<string?> ReadUntilAsync(this StreamReader reader, char delimiter) {
|
||||
return ReadUntilAsync(reader, new char[] { delimiter });
|
||||
}
|
||||
|
||||
public static async Task<string?> ReadUntilAsync(this StreamReader reader, char[] delimiter) {
|
||||
var buf = new char[512];
|
||||
int bufSize = 0;
|
||||
var tmpBuf = new char[1];
|
||||
while (!reader.EndOfStream && bufSize < buf.Length - 1) {
|
||||
if ((await reader.ReadAsync(tmpBuf, 0, 1)) != 1)
|
||||
return null;
|
||||
buf[bufSize++] = tmpBuf[0];
|
||||
|
||||
if (bufSize >= delimiter.Length && CompareBuffers(buf, bufSize - delimiter.Length, delimiter, 0, delimiter.Length) == 0)
|
||||
return new string(buf, 0, bufSize);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ namespace Elwig.Helpers.Weighing {
|
||||
Model = model;
|
||||
IsReady = true;
|
||||
HasFillingClearance = false;
|
||||
Stream.WriteTimeout = -1;
|
||||
Stream.ReadTimeout = -1;
|
||||
BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
|
||||
BackgroundThread.Start();
|
||||
}
|
||||
@ -46,7 +48,8 @@ namespace Elwig.Helpers.Weighing {
|
||||
while (IsRunning) {
|
||||
try {
|
||||
var data = await Receive();
|
||||
RaiseWeighingEvent(new WeighingEventArgs(data));
|
||||
if (data != null)
|
||||
RaiseWeighingEvent(new WeighingEventArgs(data.Value));
|
||||
} catch (Exception ex) {
|
||||
MessageBox.Show($"Beim Wiegen ist ein Fehler Aufgetreten:\n\n{ex.Message}", "Waagenfehler",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
@ -54,13 +57,12 @@ namespace Elwig.Helpers.Weighing {
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Receive() {
|
||||
string? line = null;
|
||||
using (var reader = new StreamReader(Stream, Encoding.ASCII, false, -1, true)) {
|
||||
line = await reader.ReadLineAsync();
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
|
||||
}
|
||||
if (line == null || line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
|
||||
protected async Task<WeighingResult?> Receive() {
|
||||
var line = await Reader.ReadUntilAsync("\r\n");
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
|
||||
if (line == null || line == "") {
|
||||
return null;
|
||||
} else if (line.Length != 35 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
|
||||
throw new IOException($"Invalid event from scale: '{line}'");
|
||||
}
|
||||
|
||||
|
122
Elwig/Helpers/Weighing/GassnerScale.cs
Normal file
122
Elwig/Helpers/Weighing/GassnerScale.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public class GassnerScale : Scale, ICommandScale {
|
||||
|
||||
public string Manufacturer => "Gassner";
|
||||
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 GassnerScale(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 Task SendCommand(char command) => SendCommand(Convert.ToByte(command));
|
||||
|
||||
protected async Task SendCommand(byte command) {
|
||||
byte[] cmd = [command];
|
||||
await Stream.WriteAsync(cmd);
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, Encoding.ASCII.GetString(cmd));
|
||||
}
|
||||
|
||||
protected async Task<string> ReceiveResponse() {
|
||||
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");
|
||||
}
|
||||
|
||||
var status = line[1..3];
|
||||
if (status[0] == 'E' || status[1] != 'S') {
|
||||
string msg = $"Unbekannter Fehler (Fehler code {status})";
|
||||
switch (status[1]) {
|
||||
case 'M': msg = "Waage in Bewegung"; break;
|
||||
}
|
||||
throw new IOException($"Waagenfehler {status}: {msg}");
|
||||
} else if (status[0] != ' ') {
|
||||
throw new IOException($"Invalid response from scale (error code {status})");
|
||||
}
|
||||
|
||||
return line[1..^1];
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
|
||||
await SendCommand(incIdentNr ? '\x05' : '?');
|
||||
string record = await ReceiveResponse();
|
||||
if (record.Length != 45)
|
||||
throw new IOException("Invalid response from scale: Received record has invalid size");
|
||||
var line = record[2..];
|
||||
|
||||
var status = line[ 0.. 2];
|
||||
var brutto = line[ 2.. 9].Trim();
|
||||
var tara = line[ 9..16].Trim();
|
||||
var netto = line[16..23].Trim();
|
||||
var scaleNr = line[23..25].Trim();
|
||||
var identNr = line[25..31].Trim();
|
||||
var date = line[31..39];
|
||||
var time = line[39..45];
|
||||
|
||||
identNr = identNr.Length > 0 && identNr != "0" ? identNr : null;
|
||||
var parsedDate = DateOnly.ParseExact(date, "yyyyMMdd");
|
||||
return new() {
|
||||
Weight = int.Parse(netto),
|
||||
WeighingId = identNr,
|
||||
FullWeighingId = identNr,
|
||||
Date = parsedDate,
|
||||
Time = TimeOnly.ParseExact(time, "HHmmss"),
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<WeighingResult> Weigh() {
|
||||
return await Weigh(true);
|
||||
}
|
||||
|
||||
public async Task<WeighingResult> 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;
|
||||
}
|
||||
}
|
||||
|
||||
protected 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;
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task GrantFillingClearance() {
|
||||
await SetFillingClearance(true);
|
||||
}
|
||||
|
||||
public async Task RevokeFillingClearance() {
|
||||
await SetFillingClearance(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
using System.IO;
|
||||
using System.Net.Sockets;
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Elwig.Helpers.Weighing {
|
||||
public abstract class Scale : IDisposable {
|
||||
@ -12,6 +13,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
|
||||
protected TcpClient? Tcp = null;
|
||||
protected Stream Stream;
|
||||
protected StreamReader Reader;
|
||||
|
||||
protected readonly Output? EmptyMode = null;
|
||||
protected readonly Output? FillingClearanceMode = null;
|
||||
@ -26,6 +28,8 @@ namespace Elwig.Helpers.Weighing {
|
||||
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);
|
||||
} else if (config.Type == "Gassner") {
|
||||
return new GassnerScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||
} else {
|
||||
throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
|
||||
}
|
||||
@ -41,6 +45,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
} else {
|
||||
throw new ArgumentException($"Unsupported scheme: \"{cnx.Split(':')[0]}\"");
|
||||
}
|
||||
Reader = new(Stream, Encoding.ASCII, false, 512);
|
||||
|
||||
LogPath = log;
|
||||
|
||||
@ -73,6 +78,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Reader.Close();
|
||||
Stream.Close();
|
||||
Serial?.Close();
|
||||
ControlSerialEmpty?.Close();
|
||||
|
@ -31,12 +31,9 @@ namespace Elwig.Helpers.Weighing {
|
||||
}
|
||||
|
||||
protected async Task<string> ReceiveResponse() {
|
||||
string? line = null;
|
||||
using (var reader = new StreamReader(Stream, Encoding.ASCII, false, -1, true)) {
|
||||
line = await reader.ReadLineAsync();
|
||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
|
||||
}
|
||||
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith('>')) {
|
||||
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");
|
||||
}
|
||||
|
||||
@ -68,7 +65,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
throw new IOException($"Invalid response from scale (error code {error})");
|
||||
}
|
||||
|
||||
return line[1..^1];
|
||||
return line[1..^3];
|
||||
}
|
||||
|
||||
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
|
||||
@ -82,7 +79,7 @@ namespace Elwig.Helpers.Weighing {
|
||||
var date = line[ 2..10];
|
||||
var time = line[10..15];
|
||||
var identNr = line[15..19].Trim();
|
||||
var scaleNr = line[19..20];
|
||||
var scaleNr = line[19..20].Trim();
|
||||
var brutto = line[20..28].Trim();
|
||||
var tara = line[28..36].Trim();
|
||||
var netto = line[36..44].Trim();
|
||||
|
Reference in New Issue
Block a user