Weighing: Add Gassner scale and update tests

This commit is contained in:
2024-04-13 18:00:17 +02:00
parent c6905bbb42
commit 443e111594
12 changed files with 626 additions and 21 deletions

View 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;
}
}
}

View File

@ -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}'");
} }

View 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);
}
}
}

View File

@ -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();

View File

@ -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();

View File

@ -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();
} }
} }
} }

View 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),
}));
}
}
}

View 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());
}
}
}

View File

@ -1,6 +1,6 @@
namespace Tests.WeighingTests { namespace Tests.WeighingTests {
[TestFixture] [TestFixture]
public class ScaleTestWolkersdorf { public class ScaleTestHaugsdorf {
// TODO // TODO
} }
} }

View File

@ -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);

View File

@ -1,6 +1,6 @@
namespace Tests.WeighingTests { namespace Tests.WeighingTests {
[TestFixture] [TestFixture]
public class ScaleTestGrInzersdorf { public class ScaleTestSitzendorf {
// TODO // TODO
} }
} }

View 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());
}
}
}