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(); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| using System.Net.Sockets; | ||||
| using System.Net; | ||||
| using System.Text; | ||||
| using System.IO; | ||||
|  | ||||
| namespace Tests.WeighingTests { | ||||
|     public abstract class MockScale : IDisposable { | ||||
| @@ -63,9 +64,46 @@ namespace Tests.WeighingTests { | ||||
|     } | ||||
|  | ||||
|     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) : | ||||
|             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 { | ||||
|     [TestFixture] | ||||
|     public class ScaleTestWolkersdorf { | ||||
|     public class ScaleTestHaugsdorf { | ||||
|         // TODO | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ | ||||
| 
 | ||||
| namespace Tests.WeighingTests { | ||||
|     [TestFixture] | ||||
|     class ScaleTestMatzen { | ||||
|     class ScaleTestMatzenIT3000A { | ||||
| 
 | ||||
|         private MockScale? Mock; | ||||
|         private SysTecITScale? Scale; | ||||
| @@ -43,7 +43,7 @@ namespace Tests.WeighingTests { | ||||
|                 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" + | ||||
|                 $"{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); | ||||
|             if (crc) checksum += 10; | ||||
|             return ($"<{data}{checksum,8}>\r\n", incr); | ||||
| @@ -1,6 +1,6 @@ | ||||
| namespace Tests.WeighingTests { | ||||
|     [TestFixture] | ||||
|     public class ScaleTestGrInzersdorf { | ||||
|     public class ScaleTestSitzendorf { | ||||
|         // 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