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;
|
Model = model;
|
||||||
IsReady = true;
|
IsReady = true;
|
||||||
HasFillingClearance = false;
|
HasFillingClearance = false;
|
||||||
|
Stream.WriteTimeout = -1;
|
||||||
|
Stream.ReadTimeout = -1;
|
||||||
BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
|
BackgroundThread = new Thread(new ParameterizedThreadStart(BackgroundLoop));
|
||||||
BackgroundThread.Start();
|
BackgroundThread.Start();
|
||||||
}
|
}
|
||||||
@ -46,7 +48,8 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
while (IsRunning) {
|
while (IsRunning) {
|
||||||
try {
|
try {
|
||||||
var data = await Receive();
|
var data = await Receive();
|
||||||
RaiseWeighingEvent(new WeighingEventArgs(data));
|
if (data != null)
|
||||||
|
RaiseWeighingEvent(new WeighingEventArgs(data.Value));
|
||||||
} catch (Exception ex) {
|
} 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}", "Waagenfehler",
|
||||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
@ -54,13 +57,12 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<WeighingResult> Receive() {
|
protected async Task<WeighingResult?> Receive() {
|
||||||
string? line = null;
|
var line = await Reader.ReadUntilAsync("\r\n");
|
||||||
using (var reader = new StreamReader(Stream, Encoding.ASCII, false, -1, true)) {
|
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
|
||||||
line = await reader.ReadLineAsync();
|
if (line == null || line == "") {
|
||||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
|
return null;
|
||||||
}
|
} else if (line.Length != 35 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
|
||||||
if (line == null || line.Length != 33 || line[0] != ' ' || line[9] != ' ' || line[15] != ' ' || line[20] != ' ' || line[32] != ' ') {
|
|
||||||
throw new IOException($"Invalid event from scale: '{line}'");
|
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.IO;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Elwig.Helpers.Weighing {
|
namespace Elwig.Helpers.Weighing {
|
||||||
public abstract class Scale : IDisposable {
|
public abstract class Scale : IDisposable {
|
||||||
@ -12,6 +13,7 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
|
protected SerialPort? ControlSerialEmpty = null, ControlSerialFilling = null;
|
||||||
protected TcpClient? Tcp = null;
|
protected TcpClient? Tcp = null;
|
||||||
protected Stream Stream;
|
protected Stream Stream;
|
||||||
|
protected StreamReader Reader;
|
||||||
|
|
||||||
protected readonly Output? EmptyMode = null;
|
protected readonly Output? EmptyMode = null;
|
||||||
protected readonly Output? FillingClearanceMode = 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);
|
return new SysTecITScale(config.Id, config.Model!, config.Connection!, config.Empty, config.Filling, limit, config.Log);
|
||||||
} else if (config.Type == "Avery-Async") {
|
} 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.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 {
|
} else {
|
||||||
throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
|
throw new ArgumentException($"Invalid scale type: \"{config.Type}\"");
|
||||||
}
|
}
|
||||||
@ -41,6 +45,7 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
} else {
|
} else {
|
||||||
throw new ArgumentException($"Unsupported scheme: \"{cnx.Split(':')[0]}\"");
|
throw new ArgumentException($"Unsupported scheme: \"{cnx.Split(':')[0]}\"");
|
||||||
}
|
}
|
||||||
|
Reader = new(Stream, Encoding.ASCII, false, 512);
|
||||||
|
|
||||||
LogPath = log;
|
LogPath = log;
|
||||||
|
|
||||||
@ -73,6 +78,7 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose() {
|
public void Dispose() {
|
||||||
|
Reader.Close();
|
||||||
Stream.Close();
|
Stream.Close();
|
||||||
Serial?.Close();
|
Serial?.Close();
|
||||||
ControlSerialEmpty?.Close();
|
ControlSerialEmpty?.Close();
|
||||||
|
@ -31,12 +31,9 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected async Task<string> ReceiveResponse() {
|
protected async Task<string> ReceiveResponse() {
|
||||||
string? line = null;
|
var line = await Reader.ReadUntilAsync("\r\n");
|
||||||
using (var reader = new StreamReader(Stream, Encoding.ASCII, false, -1, true)) {
|
if (LogPath != null) await File.AppendAllTextAsync(LogPath, line);
|
||||||
line = await reader.ReadLineAsync();
|
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith(">\r\n")) {
|
||||||
if (LogPath != null) await File.AppendAllTextAsync(LogPath, $"{line}\r\n");
|
|
||||||
}
|
|
||||||
if (line == null || line.Length < 4 || !line.StartsWith('<') || !line.EndsWith('>')) {
|
|
||||||
throw new IOException("Invalid response from scale");
|
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})");
|
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) {
|
protected async Task<WeighingResult> Weigh(bool incIdentNr) {
|
||||||
@ -82,7 +79,7 @@ namespace Elwig.Helpers.Weighing {
|
|||||||
var date = line[ 2..10];
|
var date = line[ 2..10];
|
||||||
var time = line[10..15];
|
var time = line[10..15];
|
||||||
var identNr = line[15..19].Trim();
|
var identNr = line[15..19].Trim();
|
||||||
var scaleNr = line[19..20];
|
var scaleNr = line[19..20].Trim();
|
||||||
var brutto = line[20..28].Trim();
|
var brutto = line[20..28].Trim();
|
||||||
var tara = line[28..36].Trim();
|
var tara = line[28..36].Trim();
|
||||||
var netto = line[36..44].Trim();
|
var netto = line[36..44].Trim();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Tests.WeighingTests {
|
namespace Tests.WeighingTests {
|
||||||
public abstract class MockScale : IDisposable {
|
public abstract class MockScale : IDisposable {
|
||||||
@ -63,9 +64,46 @@ namespace Tests.WeighingTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public class EventMockScale : MockScale {
|
public class EventMockScale : MockScale {
|
||||||
|
|
||||||
|
private readonly Func<int, string?, int, (string, bool)> Handler;
|
||||||
|
private readonly Thread ServerThread;
|
||||||
|
private TcpClient? Client;
|
||||||
|
private bool IsRunning = true;
|
||||||
|
|
||||||
public EventMockScale(int port, Func<int, string?, int, (string, bool)> handler) :
|
public EventMockScale(int port, Func<int, string?, int, (string, bool)> handler) :
|
||||||
base(port) {
|
base(port) {
|
||||||
// TODO
|
Handler = handler;
|
||||||
|
ServerThread = new Thread(new ParameterizedThreadStart(Serve));
|
||||||
|
ServerThread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Serve(object? parameters) {
|
||||||
|
while (IsRunning) {
|
||||||
|
try {
|
||||||
|
Client = await Server.AcceptTcpClientAsync();
|
||||||
|
} catch (Exception) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Weigh(int weight) {
|
||||||
|
Weight = weight;
|
||||||
|
await Weigh();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task Weigh() {
|
||||||
|
var (res, inc) = Handler(Weight, Error, IdentNr + 1);
|
||||||
|
if (inc) IdentNr++;
|
||||||
|
await Client!.GetStream().WriteAsync(Encoding.ASCII.GetBytes(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Dispose() {
|
||||||
|
Client?.Dispose();
|
||||||
|
IsRunning = false;
|
||||||
|
ServerThread.Interrupt();
|
||||||
|
ServerThread.Join();
|
||||||
|
base.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
70
Tests/WeighingTests/ScaleTestBadenL320.cs
Normal file
70
Tests/WeighingTests/ScaleTestBadenL320.cs
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
using Elwig.Helpers.Weighing;
|
||||||
|
|
||||||
|
namespace Tests.WeighingTests {
|
||||||
|
[TestFixture]
|
||||||
|
public class ScaleTestBadenL320 {
|
||||||
|
|
||||||
|
private EventMockScale? Mock;
|
||||||
|
private AveryEventScale? Scale;
|
||||||
|
|
||||||
|
private static (string, bool) ScaleHandler(int weight, string? error, int identNr) {
|
||||||
|
var modes = error?.Split(';') ?? [];
|
||||||
|
var invalid = modes.Contains("invalid");
|
||||||
|
var unit = modes.Contains("unit");
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return ("abcd\r\n", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool incr = true;
|
||||||
|
return ($" {new DateTime(2020, 9, 28, 9, 8, 0):dd.MM.yy HH:mm} {identNr,4} {weight,9}{(unit ? "lb" : "kg")} \r\n", incr);
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void SetupScale() {
|
||||||
|
Mock = new EventMockScale(12345, ScaleHandler);
|
||||||
|
Scale = new("1", "L320", "tcp://127.0.0.1:12345");
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeTearDown]
|
||||||
|
public void TeardownScale() {
|
||||||
|
Mock?.Dispose();
|
||||||
|
Scale?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void ResetScale() {
|
||||||
|
Mock!.IdentNr = 0;
|
||||||
|
Mock!.Weight = 0;
|
||||||
|
Mock!.Error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_01_Normal() {
|
||||||
|
WeighingResult? res = null;
|
||||||
|
Scale!.WeighingEvent += (sender, evt) => {
|
||||||
|
res = evt.Result;
|
||||||
|
};
|
||||||
|
|
||||||
|
await Mock!.Weigh(2345);
|
||||||
|
await Task.Delay(100);
|
||||||
|
Assert.That(res, Is.Not.Null);
|
||||||
|
Assert.That(res, Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 2345, WeighingId = "1",
|
||||||
|
FullWeighingId = $"2020-09-28/1",
|
||||||
|
Date = new DateOnly(2020, 9, 28), Time = new TimeOnly(9, 8),
|
||||||
|
}));
|
||||||
|
|
||||||
|
await Mock!.Weigh(4215);
|
||||||
|
await Task.Delay(100);
|
||||||
|
Assert.That(res, Is.Not.Null);
|
||||||
|
Assert.That(res, Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 4215,
|
||||||
|
WeighingId = "2",
|
||||||
|
FullWeighingId = $"2020-09-28/2",
|
||||||
|
Date = new DateOnly(2020, 9, 28),
|
||||||
|
Time = new TimeOnly(9, 8),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
144
Tests/WeighingTests/ScaleTestGrInzersdorfL246.cs
Normal file
144
Tests/WeighingTests/ScaleTestGrInzersdorfL246.cs
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
using Elwig.Helpers.Weighing;
|
||||||
|
|
||||||
|
namespace Tests.WeighingTests {
|
||||||
|
[TestFixture]
|
||||||
|
class ScaleTestGrInzersdorfL246 {
|
||||||
|
|
||||||
|
private MockScale? Mock;
|
||||||
|
private SysTecITScale? Scale;
|
||||||
|
|
||||||
|
private static (string, bool) ScaleHandler(string req, int weight, string? error, int identNr) {
|
||||||
|
var modes = error?.Split(';') ?? [];
|
||||||
|
var overloaded = modes.Contains("overloaded");
|
||||||
|
var moving = modes.Contains("moving");
|
||||||
|
var invalid = modes.Contains("invalid");
|
||||||
|
var crc = modes.Contains("crc");
|
||||||
|
var unit = modes.Contains("unit");
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return ("abcd\r\n", false);
|
||||||
|
} else if (!req.StartsWith('<') || !req.EndsWith('>')) {
|
||||||
|
return ("<31>\r\n", false);
|
||||||
|
}
|
||||||
|
req = req[1..^1];
|
||||||
|
|
||||||
|
bool incr;
|
||||||
|
if (req.Length > 3) {
|
||||||
|
return ("<32>\r\n", false);
|
||||||
|
} else if (req.StartsWith("RN")) {
|
||||||
|
incr = true;
|
||||||
|
} else if (req.StartsWith("RM")) {
|
||||||
|
incr = false;
|
||||||
|
} else {
|
||||||
|
return ("<32>\r\n", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overloaded)
|
||||||
|
return ("<12>\r\n", false);
|
||||||
|
|
||||||
|
if (moving && incr)
|
||||||
|
return ("<13>\r\n", false);
|
||||||
|
|
||||||
|
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 17, 14, 23, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
|
||||||
|
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {"001",3}";
|
||||||
|
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
|
||||||
|
if (crc) checksum += 10;
|
||||||
|
return ($"<{data}{checksum,8}>\r\n", incr);
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void SetupScale() {
|
||||||
|
Mock = new CommandMockScale(12345, ScaleHandler);
|
||||||
|
Scale = new("1", "L246", "tcp://127.0.0.1:12345");
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeTearDown]
|
||||||
|
public void TeardownScale() {
|
||||||
|
Mock?.Dispose();
|
||||||
|
Scale?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void ResetScale() {
|
||||||
|
Mock!.IdentNr = 0;
|
||||||
|
Mock!.Weight = 0;
|
||||||
|
Mock!.Error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_01_CurrentWeight() {
|
||||||
|
Mock!.Weight = 1235;
|
||||||
|
Assert.That(await Scale!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1235, Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
|
||||||
|
}));
|
||||||
|
Mock!.Weight = 1240;
|
||||||
|
Assert.That(await Scale!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1240, Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
|
||||||
|
}));
|
||||||
|
Mock!.Weight = 1245;
|
||||||
|
Assert.That(await Scale!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1245, Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_02_Normal() {
|
||||||
|
Mock!.Weight = 1235;
|
||||||
|
Assert.That(await Scale!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1235, WeighingId = "1",
|
||||||
|
FullWeighingId = $"2020-10-17/1",
|
||||||
|
Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
|
||||||
|
}));
|
||||||
|
Mock!.Weight = 3335;
|
||||||
|
Assert.That(await Scale!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 3335, WeighingId = "2",
|
||||||
|
FullWeighingId = $"2020-10-17/2",
|
||||||
|
Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
|
||||||
|
}));
|
||||||
|
Mock!.Weight = 6420;
|
||||||
|
Assert.That(await Scale!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 6420, WeighingId = "3",
|
||||||
|
FullWeighingId = $"2020-10-17/3",
|
||||||
|
Date = new DateOnly(2020, 10, 17), Time = new TimeOnly(14, 23),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_03_Moving() {
|
||||||
|
Mock!.Weight = 1_000;
|
||||||
|
Mock!.Error = "moving";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||||
|
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_04_Overloaded() {
|
||||||
|
Mock!.Weight = 10_000;
|
||||||
|
Mock!.Error = "overloaded";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||||
|
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_05_InvalidResponse() {
|
||||||
|
Mock!.Weight = 1_000;
|
||||||
|
Mock!.Error = "invalid";
|
||||||
|
Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_06_InvalidCrc() {
|
||||||
|
Mock!.Weight = 1_000;
|
||||||
|
Mock!.Error = "crc";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||||
|
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_07_InvalidUnit() {
|
||||||
|
Mock!.Weight = 1_000;
|
||||||
|
Mock!.Error = "unit";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await Scale!.Weigh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
namespace Tests.WeighingTests {
|
namespace Tests.WeighingTests {
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ScaleTestWolkersdorf {
|
public class ScaleTestHaugsdorf {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Tests.WeighingTests {
|
namespace Tests.WeighingTests {
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
class ScaleTestMatzen {
|
class ScaleTestMatzenIT3000A {
|
||||||
|
|
||||||
private MockScale? Mock;
|
private MockScale? Mock;
|
||||||
private SysTecITScale? Scale;
|
private SysTecITScale? Scale;
|
||||||
@ -43,7 +43,7 @@ namespace Tests.WeighingTests {
|
|||||||
return ("<13>\r\n", false);
|
return ("<13>\r\n", false);
|
||||||
|
|
||||||
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 15, 12, 34, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
|
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 15, 12, 34, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
|
||||||
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {1,3}";
|
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {"1",3}";
|
||||||
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
|
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
|
||||||
if (crc) checksum += 10;
|
if (crc) checksum += 10;
|
||||||
return ($"<{data}{checksum,8}>\r\n", incr);
|
return ($"<{data}{checksum,8}>\r\n", incr);
|
@ -1,6 +1,6 @@
|
|||||||
namespace Tests.WeighingTests {
|
namespace Tests.WeighingTests {
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ScaleTestGrInzersdorf {
|
public class ScaleTestSitzendorf {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
163
Tests/WeighingTests/ScaleTestWolkersdorfIT6000E.cs
Normal file
163
Tests/WeighingTests/ScaleTestWolkersdorfIT6000E.cs
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
using Elwig.Helpers.Weighing;
|
||||||
|
|
||||||
|
namespace Tests.WeighingTests {
|
||||||
|
[TestFixture]
|
||||||
|
class ScaleTestWolkersdorfIT6000E {
|
||||||
|
|
||||||
|
private MockScale? MockA;
|
||||||
|
private MockScale? MockB;
|
||||||
|
private SysTecITScale? ScaleA;
|
||||||
|
private SysTecITScale? ScaleB;
|
||||||
|
|
||||||
|
private static (string, bool) ScaleHandler(string req, int weight, string? error, int identNr, string terminalNr) {
|
||||||
|
var modes = error?.Split(';') ?? [];
|
||||||
|
var overloaded = modes.Contains("overloaded");
|
||||||
|
var moving = modes.Contains("moving");
|
||||||
|
var invalid = modes.Contains("invalid");
|
||||||
|
var crc = modes.Contains("crc");
|
||||||
|
var unit = modes.Contains("unit");
|
||||||
|
|
||||||
|
if (invalid) {
|
||||||
|
return ("abcd\r\n", false);
|
||||||
|
} else if (!req.StartsWith('<') || !req.EndsWith('>')) {
|
||||||
|
return ("<31>\r\n", false);
|
||||||
|
}
|
||||||
|
req = req[1..^1];
|
||||||
|
|
||||||
|
bool incr;
|
||||||
|
if (req.Length > 3) {
|
||||||
|
return ("<32>\r\n", false);
|
||||||
|
} else if (req.StartsWith("RN")) {
|
||||||
|
incr = true;
|
||||||
|
} else if (req.StartsWith("RM")) {
|
||||||
|
incr = false;
|
||||||
|
} else {
|
||||||
|
return ("<32>\r\n", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (overloaded)
|
||||||
|
return ("<12>\r\n", false);
|
||||||
|
|
||||||
|
if (moving && incr)
|
||||||
|
return ("<13>\r\n", false);
|
||||||
|
|
||||||
|
string data = $"00{(moving ? 1 : 0)}0{new DateTime(2020, 10, 8, 8, 47, 0):dd.MM.yyHH:mm}{(incr ? identNr : 0),4}1" +
|
||||||
|
$"{weight,8}{0,8}{weight,8}{(unit ? "lb" : "kg")} {terminalNr,3}";
|
||||||
|
ushort checksum = Elwig.Helpers.Utils.CalcCrc16Modbus(data);
|
||||||
|
if (crc) checksum += 10;
|
||||||
|
return ($"<{data}{checksum,8}>\r\n", incr);
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public void SetupScale() {
|
||||||
|
MockA = new CommandMockScale(12345, (req, weight, error, identNr) => ScaleHandler(req, weight, error, identNr, "A"));
|
||||||
|
MockB = new CommandMockScale(12346, (req, weight, error, identNr) => ScaleHandler(req, weight, error, identNr, "B"));
|
||||||
|
ScaleA = new("A", "IT3000E", "tcp://127.0.0.1:12345");
|
||||||
|
ScaleB = new("B", "IT3000E", "tcp://127.0.0.1:12346");
|
||||||
|
}
|
||||||
|
|
||||||
|
[OneTimeTearDown]
|
||||||
|
public void TeardownScale() {
|
||||||
|
MockA?.Dispose();
|
||||||
|
MockB?.Dispose();
|
||||||
|
ScaleA?.Dispose();
|
||||||
|
ScaleB?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void ResetScale() {
|
||||||
|
MockA!.IdentNr = 0;
|
||||||
|
MockA!.Weight = 0;
|
||||||
|
MockA!.Error = null;
|
||||||
|
MockB!.IdentNr = 0;
|
||||||
|
MockB!.Weight = 0;
|
||||||
|
MockB!.Error = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_01_CurrentWeight() {
|
||||||
|
MockA!.Weight = 1234;
|
||||||
|
Assert.That(await ScaleA!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1234, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
MockB!.Weight = 3456;
|
||||||
|
Assert.That(await ScaleB!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 3456, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
MockA!.Weight = 1236;
|
||||||
|
Assert.That(await ScaleA!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1236, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
MockB!.Weight = 3457;
|
||||||
|
Assert.That(await ScaleB!.GetCurrentWeight(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 3457, Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public async Task Test_02_Normal() {
|
||||||
|
MockA!.Weight = 1234;
|
||||||
|
Assert.That(await ScaleA!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 1234, WeighingId = "1",
|
||||||
|
FullWeighingId = $"2020-10-08/1",
|
||||||
|
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
MockB!.Weight = 3456;
|
||||||
|
Assert.That(await ScaleB!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 3456, WeighingId = "1",
|
||||||
|
FullWeighingId = $"2020-10-08/1",
|
||||||
|
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
MockA!.Weight = 4321;
|
||||||
|
Assert.That(await ScaleA!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 4321, WeighingId = "2",
|
||||||
|
FullWeighingId = $"2020-10-08/2",
|
||||||
|
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
MockB!.Weight = 3333;
|
||||||
|
Assert.That(await ScaleB!.Weigh(), Is.EqualTo(new WeighingResult {
|
||||||
|
Weight = 3333, WeighingId = "2",
|
||||||
|
FullWeighingId = $"2020-10-08/2",
|
||||||
|
Date = new DateOnly(2020, 10, 8), Time = new TimeOnly(8, 47),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_03_Moving() {
|
||||||
|
MockA!.Weight = 1_000;
|
||||||
|
MockA!.Error = "moving";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||||
|
Assert.That(ex.Message, Contains.Substring("Waage in Bewegung"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_04_Overloaded() {
|
||||||
|
MockA!.Weight = 10_000;
|
||||||
|
MockA!.Error = "overloaded";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||||
|
Assert.That(ex.Message, Contains.Substring("Waage in Überlast"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_05_InvalidResponse() {
|
||||||
|
MockA!.Weight = 1_000;
|
||||||
|
MockA!.Error = "invalid";
|
||||||
|
Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_06_InvalidCrc() {
|
||||||
|
MockA!.Weight = 1_000;
|
||||||
|
MockA!.Error = "crc";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||||
|
Assert.That(ex.Message, Contains.Substring("Invalid CRC16 checksum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Test_07_InvalidUnit() {
|
||||||
|
MockA!.Weight = 1_000;
|
||||||
|
MockA!.Error = "unit";
|
||||||
|
IOException ex = Assert.ThrowsAsync<IOException>(async () => await ScaleA!.Weigh());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user